<!-- @format -->
<script lang="ts">
  import { goto } from "$app/navigation";
  import { unlockVaultWorkspace } from "$lib/backend/auth";
  import AppModal from "$lib/components/AppModal.svelte";
  import {
    deleteEntryTemplate,
    loadEntryTemplate,
    saveEntryTemplate,
  } from "$lib/backend/templates";
  import {
    deleteEntryByStoreId,
    entriesStore,
    getDefaultEntry,
    hydrateEntries,
    loadEntryByStoreId,
    saveEntryFromStore,
  } from "$lib/stores/entries";
  import {
    deleteFragmentByStoreId,
    hydrateFragments,
  } from "$lib/stores/fragments";
  import {
    deleteListByStoreId,
    hydrateLists,
    updateListByStoreId,
  } from "$lib/stores/lists";
  import {
    hydrateUiSettings,
    settingsDefaultStartupView,
    type StartupView,
  } from "$lib/stores/settings";
  import {
    isVaultReady,
    setFlushCallback,
    setVaultSession,
  } from "$lib/stores/session";
  import { deleteTodoListByStoreId, hydrateTodos } from "$lib/stores/todos";
  import Navbar from "$lib/components/Navbar.svelte";
  import SidePanel from "$lib/components/SidePanel.svelte";
  import EditorPanel from "$lib/components/EditorPanel.svelte";
  import { onMount } from "svelte";
  import { get } from "svelte/store";

  type OpenDocument = {
    id: string;
    label: string;
    initialContent: string;
    linkedFrom?: {
      id: string;
      label: string;
      initialContent: string;
      section: StartupView;
    };
  };
  type CalendarPanelState = {
    items: OpenDocument[];
    busy: boolean;
    error: string;
  };
  const MAX_CACHED_DOCUMENTS = 5;

  const initialEntry = getDefaultEntry(get(entriesStore));

  let selectedSection = "entries";
  let panelOpen = true;
  let editMode = false;
  let activeDocumentId = initialEntry?.id ?? "entries/daily-notes";
  let activeDocumentLabel = initialEntry?.label ?? "Daily Notes";
  let openDocuments: Record<string, string> = initialEntry
    ? { [initialEntry.id]: initialEntry.initialContent }
    : {
        "entries/daily-notes":
          "# Daily Notes\n\nStart writing today's entry...",
      };
  let savedDocumentContent: Record<string, string> = { ...openDocuments };
  let documentAccessOrder: string[] = Object.keys(openDocuments);
  let modalOpen = false;
  let modalTitle = "";
  let modalMessage = "";
  let modalConfirmText = "OK";
  let modalCancelText = "Cancel";
  let modalShowCancel = false;
  let modalTone: "default" | "danger" = "default";
  let modalAction:
    | "logout-confirm"
    | "logout-info"
    | "unlock-vault"
    | "delete-confirm"
    | null = null;
  let modalInputEnabled = false;
  let modalInputType = "text";
  let modalInputPlaceholder = "";
  let modalInputAriaLabel = "Modal input";
  let modalInputValue = "";
  let unlockResolver: ((password: string | null) => void) | null = null;
  let fragmentBootstrapInFlight = false;
  let pendingDeleteItemId = "";
  let templateRefreshToken = 0;
  let linkedBackTarget: OpenDocument["linkedFrom"] | null = null;
  let calendarPanelState: CalendarPanelState = {
    items: [],
    busy: false,
    error: "",
  };

  function resolveStartupSection(value: string): StartupView {
    switch (value) {
      case "calendar":
      case "fragments":
      case "todos":
      case "entries":
      case "coach":
        return value;
      default:
        return "entries";
    }
  }

  function parseSectionQuery(value: string | null): StartupView | null {
    if (!value) return null;
    const normalized = value.trim().toLowerCase();
    switch (normalized) {
      case "entries":
      case "calendar":
      case "fragments":
      case "todos":
      case "lists":
      case "coach":
        return normalized;
      default:
        return null;
    }
  }

  function sectionFromDocumentId(id: string): StartupView | null {
    if (id.startsWith("entries/")) return "entries";
    if (id.startsWith("fragments/")) return "fragments";
    if (id.startsWith("todos/")) return "todos";
    if (id.startsWith("lists/")) return "lists";
    return null;
  }

  function applyStartupSection(section: StartupView) {
    selectedSection = section;
    editMode = false;
    if (section !== "entries") {
      activeDocumentId = "";
      activeDocumentLabel = "";
    }
  }

  function toTemplatePath(id: string): string | null {
    const prefix = "entries/template-file/";
    if (!id.startsWith(prefix)) return null;
    const encoded = id.slice(prefix.length).trim();
    if (!encoded) return null;
    try {
      const decoded = decodeURIComponent(encoded);
      return decoded || null;
    } catch {
      return null;
    }
  }

  function templateNameFromPath(path: string): string {
    const fileName = path.split(/[\\/]/).pop() ?? path;
    return fileName.replace(/\.template\.md$/i, "");
  }

  function touchDocument(id: string) {
    documentAccessOrder = [
      ...documentAccessOrder.filter((item) => item !== id),
      id,
    ];
  }

  function pruneDocumentCache(preserveIds: string[] = []) {
    const preserve = new Set(
      preserveIds
        .filter(Boolean)
        .concat(activeDocumentId ? [activeDocumentId] : []),
    );
    const cachedIds = Object.keys(openDocuments);
    if (cachedIds.length <= MAX_CACHED_DOCUMENTS) return;

    let nextOpen = { ...openDocuments };
    let nextSaved = { ...savedDocumentContent };
    let nextOrder = [...documentAccessOrder];
    let currentSize = cachedIds.length;

    for (const candidate of documentAccessOrder) {
      if (currentSize <= MAX_CACHED_DOCUMENTS) break;
      if (preserve.has(candidate)) continue;
      if (!(candidate in nextOpen)) continue;

      delete nextOpen[candidate];
      delete nextSaved[candidate];
      nextOrder = nextOrder.filter((item) => item !== candidate);
      currentSize -= 1;
    }

    openDocuments = nextOpen;
    savedDocumentContent = nextSaved;
    documentAccessOrder = nextOrder;
  }

  function showModal(options: {
    action:
      | "logout-confirm"
      | "logout-info"
      | "unlock-vault"
      | "delete-confirm";
    title: string;
    message: string;
    confirmText?: string;
    cancelText?: string;
    showCancel?: boolean;
    tone?: "default" | "danger";
    inputEnabled?: boolean;
    inputType?: string;
    inputPlaceholder?: string;
    inputAriaLabel?: string;
    inputValue?: string;
  }) {
    modalAction = options.action;
    modalTitle = options.title;
    modalMessage = options.message;
    modalConfirmText = options.confirmText ?? "OK";
    modalCancelText = options.cancelText ?? "Cancel";
    modalShowCancel = options.showCancel ?? false;
    modalTone = options.tone ?? "default";
    modalInputEnabled = options.inputEnabled ?? false;
    modalInputType = options.inputType ?? "text";
    modalInputPlaceholder = options.inputPlaceholder ?? "";
    modalInputAriaLabel = options.inputAriaLabel ?? "Modal input";
    modalInputValue = options.inputValue ?? "";
    modalOpen = true;
  }

  function closeModal() {
    modalOpen = false;
    modalAction = null;
    modalInputEnabled = false;
    modalInputType = "text";
    modalInputPlaceholder = "";
    modalInputAriaLabel = "Modal input";
    modalInputValue = "";
    pendingDeleteItemId = "";
  }

  async function handleModalConfirm() {
    if (modalAction === "logout-confirm") {
      showModal({
        action: "logout-info",
        title: "Logout Requested",
        message: "You have been logged out.",
        confirmText: "Close",
      });
      return;
    }

    if (modalAction === "unlock-vault") {
      const value = modalInputValue.trim();
      if (!value) return;
      const resolve = unlockResolver;
      unlockResolver = null;
      closeModal();
      resolve?.(value);
      return;
    }

    if (modalAction === "delete-confirm") {
      const id = pendingDeleteItemId;
      closeModal();
      await performDelete(id);
      return;
    }

    closeModal();
  }

  function handleModalCancel() {
    if (modalAction === "unlock-vault") {
      const resolve = unlockResolver;
      unlockResolver = null;
      closeModal();
      resolve?.(null);
      return;
    }

    closeModal();
  }

  function requestVaultPassword(): Promise<string | null> {
    return new Promise((resolve) => {
      unlockResolver = resolve;
      showModal({
        action: "unlock-vault",
        title: "Unlock Vault",
        message: "Enter your vault password to load journal data.",
        confirmText: "Unlock",
        cancelText: "Cancel",
        showCancel: true,
        inputEnabled: true,
        inputType: "password",
        inputPlaceholder: "Vault password",
        inputAriaLabel: "Vault password",
        inputValue: "",
      });
    });
  }

  function isLockedError(error: unknown): boolean {
    const message = error instanceof Error ? error.message : String(error);
    const normalized = message.toLowerCase();
    return (
      normalized.includes("database is locked") ||
      normalized.includes("incorrect vault password")
    );
  }

  async function bootstrapFragmentsWithUnlock(maxAttempts = 3) {
    if (fragmentBootstrapInFlight) return;

    if (isVaultReady()) {
      try {
        await hydrateEntries();
        templateRefreshToken += 1;
        const firstEntry = getDefaultEntry(get(entriesStore));
        if (firstEntry && activeDocumentId === "entries/daily-notes") {
          await handleOpenDocument(firstEntry);
        }
        await hydrateFragments();
        await hydrateLists().catch(() => {});
        await hydrateTodos().catch(() => {});
      } catch (error) {
        console.error("Hydration failed:", error);
      }
      return;
    }

    fragmentBootstrapInFlight = true;
    try {
      let attempts = 0;
      while (attempts < maxAttempts) {
        try {
          const password = await requestVaultPassword();
          if (!password) return;

          await unlockVaultWorkspace(password);
          setVaultSession(password);

          await hydrateEntries();
          templateRefreshToken += 1;
          const firstEntry = getDefaultEntry(get(entriesStore));
          if (firstEntry && activeDocumentId === "entries/daily-notes") {
            await handleOpenDocument(firstEntry);
          }

          await hydrateFragments();
          await hydrateLists().catch(() => {});
          await hydrateTodos().catch(() => {});
          return;
        } catch (error) {
          if (!isLockedError(error)) return;
          attempts += 1;
        }
      }
    } finally {
      fragmentBootstrapInFlight = false;
    }
  }

  async function saveCurrentDocument() {
    if (!activeDocumentId) return;
    const content = openDocuments[activeDocumentId];
    if (!content?.trim()) return;
    if (savedDocumentContent[activeDocumentId] === content) return;

    try {
      if (
        selectedSection === "entries" &&
        activeDocumentId.startsWith("entries/template-draft-")
      ) {
        const draft = get(entriesStore).find(
          (item) => item.id === activeDocumentId,
        );
        const draftLabel = (draft?.label ?? activeDocumentLabel ?? "").trim();
        const templateName =
          draftLabel.replace(/_template$/i, "").trim() || draftLabel;
        await saveEntryTemplate({
          name: templateName,
          content,
        });
        templateRefreshToken += 1;
        entriesStore.update((items) =>
          items.filter((item) => item.id !== activeDocumentId),
        );
        const { [activeDocumentId]: _, ...rest } = openDocuments;
        openDocuments = rest;
        const { [activeDocumentId]: __, ...savedRest } = savedDocumentContent;
        savedDocumentContent = savedRest;
        documentAccessOrder = documentAccessOrder.filter(
          (item) => item !== activeDocumentId,
        );
        activeDocumentId = "";
        activeDocumentLabel = "";
        editMode = false;
      } else if (
        selectedSection === "entries" &&
        activeDocumentId.startsWith("entries/template-file/")
      ) {
        const filePath = toTemplatePath(activeDocumentId);
        if (!filePath) return;
        await saveEntryTemplate({
          name: templateNameFromPath(filePath),
          content,
          filePath,
        });
        savedDocumentContent = {
          ...savedDocumentContent,
          [activeDocumentId]: content,
        };
        templateRefreshToken += 1;
      } else if (
        selectedSection === "entries" &&
        (activeDocumentId.startsWith("entries/file/") ||
          activeDocumentId.startsWith("entries/draft-"))
      ) {
        const saved = await saveEntryFromStore(
          activeDocumentId,
          content,
          "Overwrite",
        );
        if (saved && saved.id !== activeDocumentId) {
          const { [activeDocumentId]: _, ...rest } = openDocuments;
          openDocuments = { ...rest, [saved.id]: saved.initialContent };
          const { [activeDocumentId]: __, ...savedRest } = savedDocumentContent;
          savedDocumentContent = {
            ...savedRest,
            [saved.id]: saved.initialContent,
          };
          touchDocument(saved.id);
          activeDocumentId = saved.id;
          activeDocumentLabel = saved.label;
        } else {
          savedDocumentContent = {
            ...savedDocumentContent,
            [activeDocumentId]: content,
          };
          touchDocument(activeDocumentId);
        }
      } else if (
        selectedSection === "lists" &&
        activeDocumentId.startsWith("lists/") &&
        !activeDocumentId.startsWith("lists/draft-")
      ) {
        await updateListByStoreId(activeDocumentId, undefined, content);
        savedDocumentContent = {
          ...savedDocumentContent,
          [activeDocumentId]: content,
        };
        touchDocument(activeDocumentId);
      }
      pruneDocumentCache([activeDocumentId]);
    } catch {
      // best-effort save
    }
  }

  async function handleSelect(id: string) {
    if (id === "account") {
      goto("/account");
      return;
    }

    if (id === "settings") {
      goto(`/settings?return=${encodeURIComponent(selectedSection)}`);
      return;
    }

    if (id === "logout") {
      showModal({
        action: "logout-confirm",
        title: "Confirm Logout",
        message: "Are you sure you want to log out?",
        confirmText: "Log Out",
        cancelText: "Cancel",
        showCancel: true,
        tone: "danger",
      });
      return;
    }

    if (selectedSection === id) {
      panelOpen = !panelOpen;
      return;
    }

    await saveCurrentDocument();
    selectedSection = id;
    panelOpen = true;
    activeDocumentId = "";
    activeDocumentLabel = "";
    editMode = false;
  }

  async function handleOpenDocument(doc: OpenDocument) {
    const prevActiveId = activeDocumentId;
    await saveCurrentDocument();
    editMode = false;
    linkedBackTarget = doc.linkedFrom ?? null;
    const targetSection = sectionFromDocumentId(doc.id);
    const effectiveSection = targetSection ?? selectedSection;
    if (targetSection && targetSection !== selectedSection) {
      selectedSection = targetSection;
      panelOpen = true;
    }

    // If saveCurrentDocument promoted a draft to a file-backed entry and the
    // caller passed the now-stale draft reference, the editor is already
    // showing the promoted entry — nothing more to do.
    if (doc.id === prevActiveId && activeDocumentId !== prevActiveId) {
      return;
    }

    let resolvedDoc = doc;
    if (
      effectiveSection === "entries" &&
      doc.id.startsWith("entries/file/") &&
      !doc.initialContent
    ) {
      try {
        const loaded = await loadEntryByStoreId(doc.id);
        if (loaded) {
          resolvedDoc = loaded;
        }
      } catch {
        // entry content will use initialContent fallback
      }
    } else if (
      effectiveSection === "entries" &&
      doc.id.startsWith("entries/template-file/") &&
      !doc.initialContent
    ) {
      try {
        const filePath = toTemplatePath(doc.id);
        if (filePath) {
          const loaded = await loadEntryTemplate(filePath);
          resolvedDoc = {
            id: doc.id,
            label: loaded.fileName.replace(/\.template\.md$/i, ""),
            initialContent: loaded.content,
          };
        }
      } catch {
        // template content will use initialContent fallback
      }
    }

    if (!(resolvedDoc.id in openDocuments)) {
      openDocuments = {
        ...openDocuments,
        [resolvedDoc.id]: resolvedDoc.initialContent,
      };
    }
    touchDocument(resolvedDoc.id);
    savedDocumentContent = {
      ...savedDocumentContent,
      [resolvedDoc.id]:
        openDocuments[resolvedDoc.id] ?? resolvedDoc.initialContent,
    };
    pruneDocumentCache([resolvedDoc.id]);
    activeDocumentId = resolvedDoc.id;
    activeDocumentLabel = resolvedDoc.label;
  }

  async function handleLinkedBack() {
    if (!linkedBackTarget) return;

    const target = linkedBackTarget;
    linkedBackTarget = null;
    await handleOpenDocument({
      id: target.id,
      label: target.label,
      initialContent: target.initialContent,
    });
  }

  function handleDocumentContentChange(content: string) {
    if (!activeDocumentId) return;
    openDocuments = { ...openDocuments, [activeDocumentId]: content };
    touchDocument(activeDocumentId);
    pruneDocumentCache([activeDocumentId]);
  }

  async function handleForceSave() {
    await saveCurrentDocument();
  }

  function handleDeleteDocument(id: string) {
    const { [id]: _, ...remaining } = openDocuments;
    openDocuments = remaining;
    const { [id]: __, ...savedRemaining } = savedDocumentContent;
    savedDocumentContent = savedRemaining;
    documentAccessOrder = documentAccessOrder.filter((item) => item !== id);
  }

  async function performDelete(id: string) {
    try {
      let ok = false;
      if (selectedSection === "entries") {
        const templatePath = toTemplatePath(id);
        if (templatePath) {
          ok = await deleteEntryTemplate(templatePath);
          if (ok) templateRefreshToken += 1;
        } else if (id.startsWith("entries/template-draft-")) {
          entriesStore.update((items) =>
            items.filter((item) => item.id !== id),
          );
          ok = true;
        } else {
          ok = await deleteEntryByStoreId(id);
        }
      } else if (selectedSection === "todos") {
        ok = await deleteTodoListByStoreId(id);
      } else if (selectedSection === "lists") {
        ok = await deleteListByStoreId(id);
      } else if (selectedSection === "fragments") {
        ok = await deleteFragmentByStoreId(id);
      }
      if (!ok) return;

      handleDeleteDocument(id);
      if (activeDocumentId === id) {
        activeDocumentId = "";
        activeDocumentLabel = "";
        editMode = false;
      }
    } catch (error) {
      console.error("Delete failed:", error);
    }
  }

  async function handleEditItem(doc: OpenDocument) {
    if (doc.id !== activeDocumentId) {
      await handleOpenDocument(doc);
    }
    editMode = true;
  }

  function handleDeleteItem(doc: { id: string; label: string }) {
    pendingDeleteItemId = doc.id;
    showModal({
      action: "delete-confirm",
      title: "Confirm Delete",
      message: `Are you sure you want to delete "${doc.label}"? This action cannot be undone.`,
      confirmText: "Delete",
      cancelText: "Cancel",
      showCancel: true,
      tone: "danger",
    });
  }

  onMount(() => {
    setFlushCallback(saveCurrentDocument);
    void (async () => {
      await hydrateUiSettings();
      const startupSection = resolveStartupSection(
        get(settingsDefaultStartupView),
      );
      const sectionFromQuery = parseSectionQuery(
        new URLSearchParams(window.location.search).get("section"),
      );
      applyStartupSection(sectionFromQuery ?? startupSection);
      await bootstrapFragmentsWithUnlock();
    })();
  });
</script>

<div class="app-shell" class:panel-closed={!panelOpen}>
  <Navbar activeSection={selectedSection} onSelect={handleSelect} />
  {#if panelOpen}
    <SidePanel
      activeSection={selectedSection}
      {activeDocumentId}
      {templateRefreshToken}
      onOpenDocument={handleOpenDocument}
      onEditItem={handleEditItem}
      onDeleteItem={handleDeleteItem}
      onCalendarStateChange={(state) => {
        calendarPanelState = state;
      }}
    />
  {/if}
  <EditorPanel
    activeSection={selectedSection}
    openDocumentId={activeDocumentId}
    openDocumentName={activeDocumentLabel}
    openDocumentContent={openDocuments[activeDocumentId] ?? ""}
    onDocumentContentChange={handleDocumentContentChange}
    onOpenDocument={handleOpenDocument}
    onDeleteDocument={handleDeleteDocument}
    showLinkedBackButton={linkedBackTarget !== null}
    onLinkedBack={handleLinkedBack}
    calendarItems={calendarPanelState.items}
    calendarBusy={calendarPanelState.busy}
    calendarError={calendarPanelState.error}
    previewOnly={!editMode}
    onForceSave={handleForceSave}
  />
</div>

<AppModal
  open={modalOpen}
  title={modalTitle}
  message={modalMessage}
  confirmText={modalConfirmText}
  cancelText={modalCancelText}
  showCancel={modalShowCancel}
  tone={modalTone}
  inputEnabled={modalInputEnabled}
  inputType={modalInputType}
  inputPlaceholder={modalInputPlaceholder}
  inputAriaLabel={modalInputAriaLabel}
  bind:inputValue={modalInputValue}
  onConfirm={handleModalConfirm}
  onCancel={handleModalCancel}
/>

<style>
  .app-shell {
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
  }

  .app-shell :global(.side-panel),
  .app-shell :global(.editor-panel) {
    min-height: 0;
    min-width: 0;
  }
</style>
