using System.Reflection;
using System.Text.Json;
using Microsoft.Win32;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;

namespace MyStart;

static class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
        bool minimized = args.Any(a =>
            a.Equals("--minimized", StringComparison.OrdinalIgnoreCase) ||
            a.Equals("--min", StringComparison.OrdinalIgnoreCase) ||
            a.Equals("--autostart", StringComparison.OrdinalIgnoreCase));
        Application.Run(new MainForm(minimized));
    }
}

class MainForm : Form
{
    private readonly WebView2 _web = new() { Dock = DockStyle.Fill };
    private const string Host = "mystart.local";
    private bool _pendingMaximize;

    public MainForm(bool startMinimized = false)
    {
        Text = "MyStart";
        StartPosition = FormStartPosition.CenterScreen;
        Width = 1280;
        Height = 800;
        MinimumSize = new System.Drawing.Size(480, 360);
        ShowInTaskbar = true;
        BackColor = System.Drawing.Color.Black;
        // No title bar / border. Close with Alt+F4, minimize via the taskbar button.
        FormBorderStyle = FormBorderStyle.None;

        if (startMinimized)
        {
            // Autostart: sit minimized on the taskbar; restore to a normal window on first open.
            WindowState = FormWindowState.Minimized;
            _pendingMaximize = true;
            Resize += (_, _) =>
            {
                if (_pendingMaximize && WindowState != FormWindowState.Minimized)
                {
                    _pendingMaximize = false;
                    WindowState = FormWindowState.Normal;
                }
            };
        }
        // else: leave WindowState at its default (Normal) -> starts as a normal
        // 1280x800 window, not maximized/fullscreen.

        try
        {
            var asm = Assembly.GetExecutingAssembly();
            var icoName = asm.GetManifestResourceNames()
                .FirstOrDefault(n => n.EndsWith("app.ico", StringComparison.OrdinalIgnoreCase));
            if (icoName != null)
            {
                using var s = asm.GetManifestResourceStream(icoName);
                if (s != null) Icon = new Icon(s);
            }
        }
        catch { /* icon optional */ }

        Controls.Add(_web);
        Shown += async (_, _) => await InitAsync();
    }

    // Keep a borderless maximized window from covering the taskbar.
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        MaximizedBounds = Screen.FromHandle(Handle).WorkingArea;
    }

    private async Task InitAsync()
    {
        // Persistent data dir -> localStorage (settings/bookmarks) survive across runs
        // and independent of where the .exe lives.
        string dataDir = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "MyStart");
        Directory.CreateDirectory(dataDir);

        var env = await CoreWebView2Environment.CreateAsync(null, dataDir);
        await _web.EnsureCoreWebView2Async(env);

        // Extract the embedded app to a fixed folder, served via a stable origin.
        string appDir = Path.Combine(dataDir, "app");
        Directory.CreateDirectory(appDir);
        string htmlPath = Path.Combine(appDir, "index.html");
        var asm = Assembly.GetExecutingAssembly();
        string resName = asm.GetManifestResourceNames()
            .First(n => n.EndsWith("app-index.html", StringComparison.OrdinalIgnoreCase));
        await using (var res = asm.GetManifestResourceStream(resName)!)
        await using (var file = File.Create(htmlPath))
            await res.CopyToAsync(file);

        var core = _web.CoreWebView2;
        core.SetVirtualHostNameToFolderMapping(Host, appDir, CoreWebView2HostResourceAccessKind.Allow);
        core.Settings.IsStatusBarEnabled = false;
        core.Settings.AreDefaultContextMenusEnabled = true;
        core.Settings.IsWebMessageEnabled = true;

        // Autostart toggle from the in-app "MyStart" info page.
        core.WebMessageReceived += (_, e) =>
        {
            try
            {
                using var doc = JsonDocument.Parse(e.WebMessageAsJson);
                var root = doc.RootElement;
                if (!root.TryGetProperty("type", out var t)) return;
                switch (t.GetString())
                {
                    case "autostart-get":
                        PostAutostartState();
                        break;
                    case "autostart-set":
                        bool on = root.TryGetProperty("value", out var v) &&
                                  v.ValueKind == JsonValueKind.True;
                        SetAutostart(on);
                        PostAutostartState();
                        break;
                    case "window-drag":
                        StartWindowDrag();
                        break;
                    case "window-resize":
                        if (root.TryGetProperty("edge", out var edgeProp))
                            StartWindowResize(edgeProp.GetString());
                        break;
                    case "window-min":
                        WindowState = FormWindowState.Minimized;
                        break;
                    case "window-max":
                        WindowState = WindowState == FormWindowState.Maximized
                            ? FormWindowState.Normal : FormWindowState.Maximized;
                        break;
                    case "window-close":
                        Close();
                        break;
                }
            }
            catch { /* ignore malformed messages */ }
        };

        // Bookmark links that open a new window -> user's default browser.
        core.NewWindowRequested += (_, e) =>
        {
            e.Handled = true;
            OpenExternal(e.Uri);
        };

        // Same-tab clicks to external sites -> default browser, keep the app loaded.
        core.NavigationStarting += (_, e) =>
        {
            var uri = e.Uri ?? string.Empty;
            if (uri.StartsWith("https://" + Host, StringComparison.OrdinalIgnoreCase)) return; // internal
            if (uri.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
                uri.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
            {
                e.Cancel = true;
                OpenExternal(uri);
            }
        };

        core.Navigate("https://" + Host + "/index.html");
    }

    // --- Autostart (per-user HKCU Run key, points at THIS exe with --minimized) ---
    private const string RunKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
    private const string RunValueName = "MyStart";
    private static string ExePath => Environment.ProcessPath ?? Application.ExecutablePath;

    private static bool IsAutostartEnabled()
    {
        try
        {
            using var k = Registry.CurrentUser.OpenSubKey(RunKeyPath, false);
            return k?.GetValue(RunValueName) is string v && v.Length > 0;
        }
        catch { return false; }
    }

    private static void SetAutostart(bool on)
    {
        try
        {
            using var k = Registry.CurrentUser.OpenSubKey(RunKeyPath, true)
                          ?? Registry.CurrentUser.CreateSubKey(RunKeyPath, true);
            if (k == null) return;
            if (on) k.SetValue(RunValueName, "\"" + ExePath + "\" --minimized");
            else if (k.GetValue(RunValueName) != null) k.DeleteValue(RunValueName, false);
        }
        catch { /* ignore */ }
    }

    private void PostAutostartState()
    {
        var json = "{\"type\":\"autostart\",\"enabled\":" +
                   (IsAutostartEnabled() ? "true" : "false") + "}";
        try { _web.CoreWebView2.PostWebMessageAsJson(json); } catch { }
    }

    // --- Free window drag for the borderless window (page sends "window-drag"
    // on grip mousedown / Alt+drag; we hand off to the native move loop). ---
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern bool ReleaseCapture();
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    private const int WM_NCLBUTTONDOWN = 0xA1;
    private const int HTCAPTION = 0x2;

    private void StartWindowDrag()
    {
        // A maximized window can't be moved -> restore first, centred under cursor-ish.
        if (WindowState == FormWindowState.Maximized)
            WindowState = FormWindowState.Normal;
        ReleaseCapture();
        SendMessage(Handle, WM_NCLBUTTONDOWN, (IntPtr)HTCAPTION, IntPtr.Zero);
    }

    // --- Free window resize for the borderless window. The WebView2 control
    // fills the entire client area, so the OS never gets a chance to hit-test
    // the window's own edges for a resize cursor/drag -- every mouse message
    // lands in the browser first. The page (bundle.js) instead watches the
    // mouse position itself, and on mousedown within a few px of an edge/
    // corner sends "window-resize" with which edge; we hand off to the same
    // native resize loop Windows uses for a normal sizable window. ---
    private static readonly Dictionary<string, int> ResizeEdges = new()
    {
        ["n"] = 12, ["s"] = 15, ["e"] = 11, ["w"] = 10,
        ["ne"] = 14, ["nw"] = 13, ["se"] = 17, ["sw"] = 16,
    };

    private void StartWindowResize(string? edge)
    {
        // Only a normal (non-maximized, non-minimized) window can be resized this way.
        if (WindowState != FormWindowState.Normal) return;
        if (edge == null || !ResizeEdges.TryGetValue(edge, out var htCode)) return;
        ReleaseCapture();
        SendMessage(Handle, WM_NCLBUTTONDOWN, (IntPtr)htCode, IntPtr.Zero);
    }

    private static void OpenExternal(string uri)
    {
        try
        {
            System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
            {
                FileName = uri,
                UseShellExecute = true
            });
        }
        catch { /* ignore */ }
    }
}
