<!-- @format -->
<script lang="ts">
  import {
    probeMicrophoneAccess,
    startSpeechDictation,
    stopSpeechDictation,
  } from "$lib/backend/speech";
  import { isTauriRuntime } from "$lib/runtime/invoke";
  import {
    createFragmentFromParsed,
    deleteFragmentByStoreId,
    fragmentsStore,
    hasFragment,
    parseFragmentContent,
    serializeFragment,
    updateFragmentFromParsed,
    type FragmentItem,
  } from "$lib/stores/fragments";
  import { settingsFragmentTypes, settingsTags } from "$lib/stores/settings";
  import { renderMarkdown } from "$lib/utils/markdown";
  import { onDestroy, onMount } from "svelte";
  import { get } from "svelte/store";

  export let openDocumentId = "";
  export let openDocumentName = "";
  export let openDocumentContent = "";
  export let onDocumentContentChange: (content: string) => void = () => {};
  export let onOpenDocument: (doc: {
    id: string;
    label: string;
    initialContent: string;
  }) => void = () => {};
  export let onDeleteDocument: (id: string) => void = () => {};
  export let externalEditRequested = false;

  let fragmentTitle = "";
  let fragmentType = "";
  let customFragmentType = "";
  let fragmentTag = "";
  let customFragmentTags = "";
  let fragmentBody = "";
  let fragmentMode: "view" | "edit" | "create" = "view";
  let lastFragmentDocumentId = "";
  let fragmentTypeOptions: string[] = [];
  let tagOptions: string[] = [];
  let suppressExternalEditRequest = false;
  let dictationBusy = false;
  let dictationActive = false;
  let dictationError = "";
  let dictationStatus = "";
  let unlistenTranscript: (() => void) | null = null;
  let unlistenSpeechStatus: (() => void) | null = null;
  const customTypeValue = "__custom_type__";
  const customTagValue = "__custom_tag__";

  function buildFragmentContent(): {
    title: string;
    resolvedType: string;
    body: string;
    content: string;
    tags: string[];
  } | null {
    const title = fragmentTitle.trim();
    if (!title) return null;
    const resolvedType =
      fragmentType === customTypeValue
        ? customFragmentType.trim()
        : fragmentType;
    if (!resolvedType) return null;
    const selectedTags =
      fragmentTag && fragmentTag !== customTagValue ? [fragmentTag] : [];
    const customTags = customFragmentTags
      .split(",")
      .map((tag) => tag.trim())
      .filter(Boolean);
    const tagList = [...selectedTags, ...customTags];
    const uniqueTagList = [
      ...new Set(tagList.map((tag) => tag.toLowerCase())),
    ].map((lower) => {
      return tagList.find((tag) => tag.toLowerCase() === lower) ?? lower;
    });
    const body = fragmentBody.trim() || "Add details for this fragment.";
    const content = serializeFragment({
      title,
      type: resolvedType,
      tags: uniqueTagList,
      body,
    });
    return { title, resolvedType, body, content, tags: uniqueTagList };
  }

  async function saveFragmentEdits() {
    try {
      const payload = buildFragmentContent();
      if (!payload) return;
      const exists = hasFragment(openDocumentId);
      if (!exists) {
        await createNewFragment();
        return;
      }

      const updated = await updateFragmentFromParsed(openDocumentId, {
        title: payload.title,
        type: payload.resolvedType,
        tags: payload.tags,
        body: payload.body,
      });
      if (!updated) return;

      onDocumentContentChange(payload.content);
      fragmentMode = "view";
    } catch (error) {
      console.error("[editor] fragment:save:error", error);
    }
  }

  async function createNewFragment() {
    try {
      const payload = buildFragmentContent();
      if (!payload) return;
      const item: FragmentItem = await createFragmentFromParsed({
        title: payload.title,
        type: payload.resolvedType,
        tags: payload.tags,
        body: payload.body,
      });
      onOpenDocument(item);
      fragmentMode = "view";
    } catch (error) {
      console.error("[editor] fragment:create:error", error);
    }
  }

  async function deleteCurrentFragment() {
    try {
      const ok = await deleteFragmentByStoreId(openDocumentId);
      if (!ok) return;
      const remaining = get(fragmentsStore);
      onDeleteDocument(openDocumentId);
      if (remaining.length > 0) {
        onOpenDocument(remaining[0]);
        fragmentMode = "view";
        return;
      }
      fragmentTitle = "";
      customFragmentType = "";
      fragmentTag = customTagValue;
      customFragmentTags = "";
      fragmentBody = "";
      fragmentMode = "create";
    } catch (error) {
      console.error("[editor] fragment:delete:error", error);
    }
  }

  function startEditFragment() {
    fragmentMode = "edit";
  }

  function cancelFragmentEdit() {
    if (dictationActive) {
      void stopDictation();
    }
    if (fragmentMode === "create") {
      fragmentMode = "view";
      suppressExternalEditRequest = true;
      return;
    }
    loadFragmentFormFromDocument();
    fragmentMode = "view";
    suppressExternalEditRequest = true;
  }

  function loadFragmentFormFromDocument() {
    const content = openDocumentContent ?? "";
    const isDraftFragment = openDocumentId.startsWith("fragments/new-");
    const parsed = parseFragmentContent(
      content,
      openDocumentName || "Untitled Fragment",
    );
    fragmentTitle = parsed.title;
    const parsedType = parsed.type;
    if (!parsedType) {
      fragmentType = fragmentTypeOptions[0] ?? customTypeValue;
      customFragmentType = "";
    } else if (fragmentTypeOptions.includes(parsedType)) {
      fragmentType = parsedType;
      customFragmentType = "";
    } else {
      fragmentType = customTypeValue;
      customFragmentType = parsedType;
    }

    const parsedTags = parsed.tags;

    if (parsedTags.length === 0) {
      fragmentTag = tagOptions[0] ?? customTagValue;
      customFragmentTags = "";
    } else {
      const primary = parsedTags[0];
      if (tagOptions.includes(primary)) {
        fragmentTag = primary;
        customFragmentTags = parsedTags.slice(1).join(", ");
      } else {
        fragmentTag = customTagValue;
        customFragmentTags = parsedTags.join(", ");
      }
    }

    fragmentBody = parsed.body;
    fragmentMode = isDraftFragment ? "create" : "view";
  }

  function appendDictationChunk(text: string) {
    const cleaned = text.trim();
    if (!cleaned) return;
    const prefix =
      fragmentBody.length > 0 &&
      !fragmentBody.endsWith(" ") &&
      !fragmentBody.endsWith("\n") &&
      !fragmentBody.endsWith("\t")
        ? " "
        : "";
    fragmentBody = `${fragmentBody}${prefix}${cleaned} `;
  }

  async function startDictation() {
    if (dictationBusy || dictationActive) return;
    if (!isTauriRuntime()) {
      dictationError = "Speech dictation is available in the desktop app only.";
      return;
    }

    dictationBusy = true;
    dictationError = "";
    dictationStatus = "Checking microphone access...";
    try {
      await probeMicrophoneAccess();
      dictationStatus = "Starting dictation...";
      const started = await Promise.race([
        startSpeechDictation(),
        new Promise<never>((_, reject) =>
          setTimeout(
            () =>
              reject(
                new Error("Timed out waiting for speech process startup."),
              ),
            8000,
          ),
        ),
      ]);
      dictationActive = true;
      if (started?.pid) {
        dictationStatus = "Dictation started.";
      }
    } catch (error) {
      dictationError = String(error);
      dictationActive = false;
    } finally {
      dictationBusy = false;
    }
  }

  async function stopDictation() {
    if (dictationBusy || !dictationActive) return;
    dictationBusy = true;
    dictationError = "";
    try {
      await stopSpeechDictation();
    } catch (error) {
      dictationError = String(error);
    } finally {
      dictationActive = false;
      dictationStatus = "Dictation stopped.";
      dictationBusy = false;
    }
  }

  async function toggleDictation() {
    if (dictationActive) {
      await stopDictation();
      return;
    }

    await startDictation();
  }

  onMount(() => {
    if (!isTauriRuntime()) return;

    let disposed = false;
    void (async () => {
      const { listen } = await import("@tauri-apps/api/event");
      const unlisten = await listen<{ text?: string }>(
        "speech-transcript",
        (event) => {
          if (disposed || !dictationActive) return;
          if (fragmentMode !== "edit" && fragmentMode !== "create") return;
          const text = event.payload?.text ?? "";
          appendDictationChunk(text);
        },
      );
      const unlistenStatus = await listen<{ state?: string; message?: string }>(
        "speech-status",
        (event) => {
          const state = event.payload?.state ?? "";
          const message = event.payload?.message ?? "";
          if (message) {
            dictationStatus = message;
          }

          if (state === "listening") {
            dictationError = "";
            return;
          }

          if (state === "error") {
            dictationError = message || "Speech process error.";
            dictationActive = false;
            return;
          }

          if (state === "stopped") {
            dictationActive = false;
          }
        },
      );

      if (disposed) {
        unlisten();
        return;
      }

      unlistenTranscript = unlisten;
      unlistenSpeechStatus = unlistenStatus;
    })();

    return () => {
      disposed = true;
      if (unlistenTranscript) {
        unlistenTranscript();
        unlistenTranscript = null;
      }
      if (unlistenSpeechStatus) {
        unlistenSpeechStatus();
        unlistenSpeechStatus = null;
      }
    };
  });

  onDestroy(() => {
    if (dictationActive) {
      void stopDictation();
    }
  });

  $: fragmentTypeOptions = $settingsFragmentTypes.length
    ? $settingsFragmentTypes
    : ["General"];
  $: tagOptions = $settingsTags;
  $: if (openDocumentId !== lastFragmentDocumentId) {
    loadFragmentFormFromDocument();
    lastFragmentDocumentId = openDocumentId;
  }
  $: if (
    !fragmentType ||
    (!fragmentTypeOptions.includes(fragmentType) &&
      fragmentType !== customTypeValue)
  ) {
    fragmentType = fragmentTypeOptions[0];
  }
  $: if (
    !fragmentTag ||
    (!tagOptions.includes(fragmentTag) && fragmentTag !== customTagValue)
  ) {
    fragmentTag = tagOptions[0] ?? customTagValue;
  }
  $: if (!externalEditRequested) {
    suppressExternalEditRequest = false;
  }
  $: if (
    externalEditRequested &&
    !suppressExternalEditRequest &&
    fragmentMode === "view"
  ) {
    fragmentMode = "edit";
  }
  $: if (fragmentMode === "view" && dictationActive) {
    void stopDictation();
  }
</script>

<section class="fragment-surface">
  {#if dictationError || dictationStatus}
    <div
      class="dictation-indicator"
      class:is-error={Boolean(dictationError)}
      role="status"
      aria-live="polite"
    >
      {dictationError || dictationStatus}
    </div>
  {/if}
  {#if fragmentMode === "view"}
    <article class="fragment-view">
      {@html renderMarkdown(openDocumentContent)}
    </article>
  {:else}
    <form
      class="fragment-form"
      on:submit|preventDefault={fragmentMode === "create"
        ? createNewFragment
        : saveFragmentEdits}
    >
      <h2>{fragmentMode === "create" ? "New Fragment" : "Edit Fragment"}</h2>
      <input
        type="text"
        placeholder="Title"
        bind:value={fragmentTitle}
        aria-label="Fragment title"
      />
      <div class="fragment-form-row">
        <select bind:value={fragmentType} aria-label="Fragment type">
          {#each fragmentTypeOptions as type}
            <option value={type}>{type}</option>
          {/each}
          <option value={customTypeValue}>Custom</option>
        </select>
        {#if fragmentType === customTypeValue}
          <input
            type="text"
            placeholder="Custom type"
            bind:value={customFragmentType}
            aria-label="Custom fragment type"
          />
        {:else}
          <input
            type="text"
            value={fragmentType}
            disabled
            aria-label="Selected fragment type"
          />
        {/if}
      </div>
      <div class="fragment-form-row">
        <select bind:value={fragmentTag} aria-label="Primary fragment tag">
          {#if tagOptions.length === 0}
            <option value={customTagValue}>Custom</option>
          {:else}
            {#each tagOptions as tag}
              <option value={tag}>{tag}</option>
            {/each}
            <option value={customTagValue}>Custom</option>
          {/if}
        </select>
        <input
          type="text"
          placeholder={fragmentTag === customTagValue
            ? "Custom tags (comma separated)"
            : "Additional tags (optional, comma separated)"}
          bind:value={customFragmentTags}
          aria-label="Custom fragment tags"
        />
      </div>
      <textarea
        rows="5"
        placeholder="Fragment text"
        bind:value={fragmentBody}
        aria-label="Fragment body"
      ></textarea>
      <div class="fragment-actions">
        <button
          type="button"
          class="fragment-secondary"
          class:is-active={dictationActive}
          on:click={() => void toggleDictation()}
          disabled={dictationBusy || !isTauriRuntime()}
          aria-label={dictationActive ? "Stop dictation" : "Start dictation"}
          title={dictationActive ? "Stop dictation" : "Start dictation"}
        >
          {dictationActive ? "Stop Dictation" : "Start Dictation"}
        </button>
        <button type="submit" class="fragment-submit"
          >{fragmentMode === "create"
            ? "Create Fragment"
            : "Save Fragment"}</button
        >
        <button
          type="button"
          class="fragment-secondary"
          on:click={cancelFragmentEdit}>Cancel</button
        >
      </div>
    </form>
  {/if}
</section>

<style>
  .fragment-surface {
    min-height: 0;
    flex: 1;
    overflow: auto;
    padding: 0 14px 14px;
    position: relative;
  }

  .dictation-indicator {
    position: absolute;
    top: 8px;
    right: 22px;
    z-index: 30;
    border: 1px solid var(--border-soft);
    border-radius: 8px;
    background: color-mix(in srgb, var(--surface-1) 94%, var(--bg-editor) 6%);
    color: var(--text-dim);
    font-size: 0.76rem;
    line-height: 1.35;
    padding: 6px 9px;
    max-width: min(56ch, 60%);
    box-shadow: 0 8px 20px
      color-mix(in srgb, var(--bg-app) 35%, transparent 65%);
    pointer-events: none;
  }

  .dictation-indicator.is-error {
    color: #fca5a5;
    border-color: color-mix(in srgb, #ef4444 45%, var(--border-soft) 55%);
    background: color-mix(in srgb, #2a1414 52%, var(--surface-1) 48%);
  }

  .fragment-form {
    width: min(100%, 920px);
    margin: 0 auto;
    border: none;
    border-radius: 0;
    background: transparent;
    padding: 28px 36px;
    display: flex;
    flex-direction: column;
    gap: 12px;
  }

  .fragment-view {
    width: min(100%, 920px);
    margin: 0 auto;
    border: none;
    border-radius: 0;
    background: transparent;
    padding: 28px 36px;
    display: flex;
    flex-direction: column;
    gap: 14px;
    color: var(--text-primary);
    font-size: 0.92rem;
    line-height: 1.65;
    font-family:
      "Segoe UI Variable", "Segoe UI", "Segoe UI Emoji", "Apple Color Emoji",
      "Noto Color Emoji", Inter, Roboto, Helvetica, Arial, sans-serif;
  }

  .fragment-view :global(h1),
  .fragment-view :global(h2),
  .fragment-view :global(h3),
  .fragment-view :global(h4),
  .fragment-view :global(h5),
  .fragment-view :global(h6) {
    margin: 0 0 8px;
  }

  .fragment-view :global(p),
  .fragment-view :global(ul),
  .fragment-view :global(ol),
  .fragment-view :global(blockquote),
  .fragment-view :global(pre) {
    margin: 0 0 10px;
  }

  .fragment-view :global(code) {
    background: var(--surface-2);
    border: 1px solid var(--border-soft);
    border-radius: 4px;
    padding: 1px 4px;
    font-family: Consolas, "Courier New", monospace;
    font-size: 0.82rem;
  }

  .fragment-view :global(.markdown-tag-list) {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 6px;
    vertical-align: middle;
  }

  .fragment-view :global(.markdown-tag-chip) {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--border-soft);
    border-radius: 999px;
    padding: 1px 8px;
    font-size: 0.74rem;
    line-height: 1.35;
    color: var(--text-muted);
    background: color-mix(in srgb, var(--surface-2) 90%, var(--bg-editor) 10%);
  }

  .fragment-form h2 {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-primary);
    margin-bottom: 2px;
  }

  .fragment-form input,
  .fragment-form select,
  .fragment-form textarea {
    width: 100%;
    border: 1px solid var(--border-soft);
    border-radius: 8px;
    background-color: color-mix(
      in srgb,
      var(--surface-1) 88%,
      var(--bg-editor) 12%
    );
    color: var(--text-primary);
    padding: 10px 11px;
    font-size: 0.88rem;
    font-family:
      "Segoe UI Variable", "Segoe UI", "Segoe UI Emoji", "Apple Color Emoji",
      "Noto Color Emoji", Inter, Roboto, Helvetica, Arial, sans-serif;
  }

  .fragment-form textarea {
    resize: vertical;
    min-height: 220px;
    line-height: 1.55;
  }

  .fragment-form-row {
    display: grid;
    grid-template-columns: 180px minmax(0, 1fr);
    gap: 8px;
  }

  .fragment-submit {
    width: fit-content;
    border-radius: 8px;
    border: 1px solid var(--border-strong);
    background: color-mix(in srgb, var(--surface-2) 84%, var(--bg-hover) 16%);
    color: var(--text-primary);
    padding: 8px 12px;
    font-size: 0.82rem;
    cursor: pointer;
  }

  .fragment-submit:hover {
    background: var(--bg-hover);
  }

  .fragment-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
  }

  .fragment-secondary {
    border-radius: 8px;
    border: 1px solid var(--border-soft);
    background: color-mix(in srgb, var(--surface-1) 90%, var(--bg-editor) 10%);
    color: var(--text-muted);
    padding: 8px 12px;
    font-size: 0.82rem;
    cursor: pointer;
  }

  .fragment-secondary:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
  }

  .fragment-secondary.is-active {
    border-color: var(--border-strong);
    color: var(--text-primary);
    background: var(--bg-active);
  }

  @media (max-width: 980px) {
    .fragment-surface {
      padding: 4px 8px 10px;
    }

    .dictation-indicator {
      right: 10px;
      top: 6px;
      max-width: calc(100% - 20px);
    }

    .fragment-form,
    .fragment-view {
      width: 100%;
      padding: 18px 16px;
      font-size: 0.89rem;
    }

    .fragment-form-row {
      grid-template-columns: 1fr;
    }
  }
</style>
