<script lang="ts">
  import { invoke } from '@tauri-apps/api/core';
  import { ListTree, Check, Timer, Loader2, ThumbsUp, ThumbsDown, Pencil } from 'lucide-svelte';
  import { profilesStore } from '$lib/stores/profiles.svelte.ts';
  import SpeakerButton from '$lib/components/SpeakerButton.svelte';

  let { parentTaskId, parentTaskText = '', reduceMotion = false } = $props();

  interface Subtask {
    id: string;
    text: string;
    done: boolean;
  }

  let subtasks = $state<Subtask[]>([]);
  let loading = $state(false);
  let error = $state('');
  let decomposing = $state(false);

  // Per-step UI state. Keyed by subtask id so we never lose state when
  // the list reorders. Values:
  //   rating[id] — 1 | -1 — the thumbs vote the user gave this session
  //   editing[id] — true while the user is editing the step text
  //   draft[id]   — the in-flight edit value before save
  let rating = $state<Record<string, 1 | -1 | undefined>>({});
  let editing = $state<Record<string, boolean>>({});
  let draft = $state<Record<string, string>>({});

  // Monotonic token per-step. Each time the user kicks off a save we
  // bump the token; the pending save remembers its token and only
  // rolls back if the failure belongs to the still-current edit.
  // Without this, a slow first save that eventually fails will stomp
  // a faster second save that already committed.
  let saveToken = $state<Record<string, number>>({});

  async function loadSubtasks() {
    loading = true;
    error = '';
    try {
      subtasks = await invoke<Subtask[]>('list_subtasks_cmd', { parentTaskId });
    } catch (e) {
      error = String(e);
    } finally {
      loading = false;
    }
  }

  async function decompose() {
    decomposing = true;
    error = '';
    try {
      subtasks = await invoke<Subtask[]>('decompose_and_store', {
        parentTaskId,
        profileId: profilesStore.activeProfileId,
      });
      // Phase 6 nudge-bus signal. The bus schedules a 15-min idle
      // check on this parent task id — if no step or the task itself
      // is completed in that window, a gentle "still with that one?"
      // nudge fires.
      if (typeof window !== 'undefined') {
        window.dispatchEvent(new CustomEvent('lumotia:microstep-generated', {
          detail: { parentTaskId },
        }));
      }
    } catch (e) {
      error = String(e);
    } finally {
      decomposing = false;
    }
  }

  async function checkStep(subtaskId: string) {
    try {
      await invoke('complete_subtask_cmd', { subtaskId });
      const idx = subtasks.findIndex(s => s.id === subtaskId);
      if (idx >= 0) subtasks[idx] = { ...subtasks[idx], done: true };
      // Phase 6 nudge-bus signal. Any completed step clears the
      // micro-step-idle timer for this parent task — the breakdown
      // is demonstrably getting worked, no nudge needed.
      if (typeof window !== 'undefined') {
        window.dispatchEvent(new CustomEvent('lumotia:step-completed', {
          detail: { id: subtaskId, parentTaskId },
        }));
      }
    } catch (_) {}
  }

  function startTimer(subtaskId: string) {
    window.dispatchEvent(new CustomEvent('lumotia:start-timer', {
      detail: { taskId: subtaskId, seconds: 120 }
    }));
  }

  // --- HITL feedback --------------------------------------------------------
  //
  // All three paths (thumbs up, thumbs down, correction-via-edit) route
  // into the same `record_feedback` command. The parent task text is the
  // "input" the AI was given, so it travels in context_json so the prompt
  // builder can reconstruct the (input, good-output) pair.

  function feedbackContextJson() {
    return JSON.stringify({ input: parentTaskText ?? '' });
  }

  async function recordThumb(step: Subtask, ratingValue: 1 | -1) {
    // Toggle: if the user already voted the same way, clear it (record
    // rating 0 means correction, not a thumb-off — we just skip the
    // re-record and drop the local highlight). Unvoting isn't stored;
    // the audit trail stays immutable.
    if (rating[step.id] === ratingValue) {
      const next = { ...rating };
      delete next[step.id];
      rating = next;
      return;
    }
    rating = { ...rating, [step.id]: ratingValue };
    try {
      await invoke('record_feedback', {
        input: {
          targetType: 'microstep',
          targetId: step.id,
          rating: ratingValue,
          originalText: step.text,
          correctedText: null,
          contextJson: feedbackContextJson(),
          profileId: profilesStore.activeProfileId,
        },
      });
    } catch (_) { /* feedback capture is best-effort, never fatal */ }
  }

  function startEdit(step: Subtask) {
    editing = { ...editing, [step.id]: true };
    draft = { ...draft, [step.id]: step.text };
  }

  function cancelEdit(stepId: string) {
    const nextE = { ...editing }; delete nextE[stepId]; editing = nextE;
    const nextD = { ...draft }; delete nextD[stepId]; draft = nextD;
  }

  async function saveEdit(step: Subtask) {
    const next = (draft[step.id] ?? '').trim();
    cancelEdit(step.id);
    if (!next || next === step.text) return;
    const original = step.text;
    // Update in-memory first so the UI is snappy; roll back if the
    // persistence call fails so we never show stale-but-different text.
    // The monotonic `myToken` guards against a stale rollback stomping
    // a fresher successful save that landed while we were waiting.
    const myToken = (saveToken[step.id] ?? 0) + 1;
    saveToken = { ...saveToken, [step.id]: myToken };
    const idx = subtasks.findIndex(s => s.id === step.id);
    if (idx >= 0) subtasks[idx] = { ...subtasks[idx], text: next };
    try {
      await invoke('update_task_cmd', {
        id: step.id,
        patch: { text: next },
      });
      // Record correction as the highest-value feedback signal.
      await invoke('record_feedback', {
        input: {
          targetType: 'microstep',
          targetId: step.id,
          rating: 0,
          originalText: original,
          correctedText: next,
          contextJson: feedbackContextJson(),
          profileId: profilesStore.activeProfileId,
        },
      }).catch(() => {});
    } catch (_) {
      // Only roll back if our token is still the most recent — a later
      // edit that already succeeded must not be overwritten.
      if (saveToken[step.id] === myToken) {
        const rollbackIdx = subtasks.findIndex(s => s.id === step.id);
        if (rollbackIdx >= 0) {
          subtasks[rollbackIdx] = { ...subtasks[rollbackIdx], text: original };
        }
      }
    }
  }

  function handleEditKeydown(evt: KeyboardEvent, step: Subtask) {
    if (evt.key === 'Enter') { evt.preventDefault(); saveEdit(step); }
    else if (evt.key === 'Escape') { evt.preventDefault(); cancelEdit(step.id); }
  }

  $effect(() => {
    if (parentTaskId) loadSubtasks();
  });
</script>

<div class="pl-6 mt-1 flex flex-col gap-1">
  {#if loading}
    <div class="flex items-center gap-2 text-[12px] text-text-secondary py-1">
      <Loader2 size={12} class="animate-spin" aria-hidden="true" />
      Loading steps…
    </div>
  {:else if subtasks.length === 0 && !error}
    <button
      class="flex items-center gap-1.5 text-[12px] text-text-secondary hover:text-accent py-1 disabled:opacity-50"
      onclick={decompose}
      disabled={decomposing}
      aria-label="Break down this task into micro-steps"
    >
      {#if decomposing}
        <Loader2 size={12} class="animate-spin" aria-hidden="true" />
        Breaking down…
      {:else}
        <ListTree size={12} aria-hidden="true" />
        Break down
      {/if}
    </button>
  {:else if error}
    <p class="text-[12px] text-text-secondary py-1">{error}</p>
  {:else}
    {#each subtasks as step (step.id)}
      <div class="flex items-center gap-2 group py-0.5">
        <button
          class="w-3.5 h-3.5 rounded border flex items-center justify-center flex-shrink-0
            {step.done
              ? 'bg-success/20 border-success text-success'
              : 'border-border-subtle hover:border-accent'}"
          onclick={() => checkStep(step.id)}
          disabled={step.done}
          aria-label={step.done ? 'Step complete' : 'Mark step complete'}
        >
          {#if step.done}
            <Check size={9} aria-hidden="true" />
          {/if}
        </button>

        {#if editing[step.id]}
          <!-- svelte-ignore a11y_autofocus — deliberate: inline edit
               is user-initiated and focus must land on the input to
               match the UX pattern users expect from any task app. -->
          <input
            type="text"
            bind:value={draft[step.id]}
            onkeydown={(e) => handleEditKeydown(e, step)}
            onblur={() => saveEdit(step)}
            class="text-[12px] flex-1 min-w-0 bg-bg-input border border-accent rounded px-1.5 py-0.5 text-text focus:outline-none"
            autofocus
            data-no-transition
          />
        {:else}
          <button
            type="button"
            class="text-[12px] flex-1 min-w-0 {step.done ? 'line-through text-text-tertiary' : 'text-text-secondary'} truncate text-left cursor-text bg-transparent border-0 p-0"
            ondblclick={() => !step.done && startEdit(step)}
            disabled={step.done}
            aria-label="Double-click to edit this step"
            title="Double-click to edit"
          >{step.text}</button>
        {/if}

        {#if !step.done && !editing[step.id]}
          <!-- HITL feedback: thumbs vote + pencil edit. All three
               route into record_feedback and feed the prompt-conditioning
               loop. See docs/roadmap/2026-04-23-... Phase 2. -->
          <button
            class="opacity-0 group-hover:opacity-100 p-0.5 text-text-tertiary hover:text-success
              {rating[step.id] === 1 ? '!opacity-100 text-success' : ''}"
            onclick={() => recordThumb(step, 1)}
            aria-label={rating[step.id] === 1 ? 'Remove thumbs up' : 'Thumbs up, this is a good step'}
            title="Thumbs up, train the model on this style"
            style={reduceMotion ? '' : 'transition: opacity var(--duration-ui), color var(--duration-ui)'}
          >
            <ThumbsUp size={10} aria-hidden="true" />
          </button>
          <button
            class="opacity-0 group-hover:opacity-100 p-0.5 text-text-tertiary hover:text-danger
              {rating[step.id] === -1 ? '!opacity-100 text-danger' : ''}"
            onclick={() => recordThumb(step, -1)}
            aria-label={rating[step.id] === -1 ? 'Remove thumbs down' : 'Thumbs down, this misses the mark'}
            title="Thumbs down, avoid this style"
            style={reduceMotion ? '' : 'transition: opacity var(--duration-ui), color var(--duration-ui)'}
          >
            <ThumbsDown size={10} aria-hidden="true" />
          </button>
          <button
            class="opacity-0 group-hover:opacity-100 p-0.5 text-text-tertiary hover:text-accent"
            onclick={() => startEdit(step)}
            aria-label="Edit this step (the correction trains future suggestions)"
            title="Edit, this is the strongest training signal"
            style={reduceMotion ? '' : 'transition: opacity var(--duration-ui)'}
          >
            <Pencil size={10} aria-hidden="true" />
          </button>
          <span
            class="opacity-0 group-hover:opacity-100"
            style={reduceMotion ? '' : 'transition: opacity var(--duration-ui)'}
          >
            <SpeakerButton text={step.text} label="Read this step aloud" size={10} />
          </span>
          <button
            class="opacity-0 group-hover:opacity-100 flex items-center gap-1 text-[12px] text-text-secondary hover:text-accent"
            onclick={() => startTimer(step.id)}
            aria-label="Start 2-minute timer for this step"
            style={reduceMotion ? '' : 'transition: opacity var(--duration-ui)'}
          >
            <Timer size={10} aria-hidden="true" />
            Just Start
          </button>
        {/if}
      </div>
    {/each}
  {/if}
</div>
