/* project-planning-state.jsx — shared planning/todos DRAFT for Project Detail.
   ─────────────────────────────────────────────────────────────────────────────
   WHY THIS EXISTS
   In the original app the Planning & Execution tab kept the editable todo draft
   *inside* ProjectPlanningExec and saved it with the "Save planning" bar. Now
   that the todos live in a separate Board tab, BOTH tabs must edit the same draft
   and save together. So the draft is lifted here into a hook that you call ONCE
   in ProjectDetailView and pass to both tabs.

   USAGE (project-detail.jsx):
     const planning = useProjectPlanning(project, onAction);
     ...
     {planning.dirty && (tab === 'planning' || tab === 'board') && <PlanningSaveBar planning={planning} />}
     {tab === 'planning' && <ProjectPlanningExec planning={planning} project={project} db={db}
                              onDrawer={onDrawer} onAction={onAction} DocumentsPanel={DocumentsPanel} Inline={Inline} />}
     {tab === 'board'    && <ProjectBoardTab planning={planning} project={project}
                              statuses={window.AC.PROJECT_TODO_STATUSES} />}

   Persistence is unchanged: onAction('updateProjectPlanning', { projectId,
   planningNotes, planningSections }). Milestones still persist
   immediately through their own onAction/onDrawer calls (handled in the tabs).

   Exports: window.useProjectPlanning, window.PlanningSaveBar
   ───────────────────────────────────────────────────────────────────────────── */
(function () {
  const { useState, useMemo, useEffect } = React;

  const gid = () => (crypto.randomUUID ? crypto.randomUUID()
    : '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) =>
      (Number(c) ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> Number(c) / 4).toString(16)));
  const isGuid = (v) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(String(v || ''));
  const cleanRequirementItems = (items) => {
    return (Array.isArray(items) ? items : [])
      .map((item, index) => ({
        id: isGuid(item.id) ? item.id : gid(),
        position: index,
        title: String(item.title || '').trim(),
        type: String(item.type || 'UI/UX').trim() || 'UI/UX',
        priority: ['low', 'high', 'critical'].includes(String(item.priority || '').toLowerCase())
          ? String(item.priority).slice(0, 1).toUpperCase() + String(item.priority).slice(1).toLowerCase()
          : 'Medium',
        status: ['accepted', 'implemented'].includes(String(item.status || '').toLowerCase()) ? String(item.status).toLowerCase() : 'draft',
      }))
      .filter((item) => item.title);
  };

  /* ── draft <-> server (de)serialization (identical to the original) ── */
  function sectionTodoPlan(section) {
    const clusters = Array.isArray(section.todoClusters) ? section.todoClusters : [];
    return {
      kind: 'clustered-todos', version: 1,
      categories: clusters.map((cluster) => ({
        id: isGuid(cluster.id) ? cluster.id : gid(),
        name: cluster.name || '',
        items: (cluster.todos || []).map((todo) => ({
          id: isGuid(todo.id) ? todo.id : gid(),
          number: Number(todo.number) || 0,
          title: todo.title || '',
          category: window.AC.cleanProjectTodoCategory(todo.category),
          done: !!todo.done,
          status: todo.status || (todo.done ? 'Done' : 'To do'),
          remarks: todo.remarks || '',
          after: todo.after || '',
          milestoneId: todo.milestoneId || '',
          gitCommitLinks: Array.isArray(todo.gitCommitLinks) ? todo.gitCommitLinks : [],
          messages: Array.isArray(todo.messages) ? todo.messages : [],
        })),
      })),
    };
  }
  function todoClustersFromPlan(section, plan) {
    return (plan.categories || []).map((category, index) => {
      const clusterId = isGuid(category.id) ? category.id : gid();
      return {
        id: clusterId, planningSectionId: section.id, position: index, name: category.name || '',
        todos: (category.items || []).map((item, itemIndex) => ({
          id: isGuid(item.id) ? item.id : gid(),
          number: Number(item.number) || 0,
          todoClusterId: clusterId, position: itemIndex,
          title: item.title || '', done: !!item.done,
          category: window.AC.cleanProjectTodoCategory(item.category),
          status: item.status || (item.done ? 'Done' : 'To do'),
          remarks: item.remarks || '', after: item.after || '',
          milestoneId: isGuid(item.milestoneId) ? item.milestoneId : null,
          gitCommitLinks: Array.isArray(item.gitCommitLinks) ? item.gitCommitLinks : [],
          messages: Array.isArray(item.messages) ? item.messages : [],
        })),
      };
    });
  }
  function projectPlanningDraft(project) {
    return window.AC.normalizeProjectPlanningSections(project.planningSections, project.planningNotes)
      .map((section) => {
        const requirementItems = cleanRequirementItems(section.requirementItems);
        return { ...section, requirementItems, todoPlan: sectionTodoPlan(section) };
      });
  }
  function serializeSections(sections) {
    return sections.map((section, index) => {
      const requirementItems = cleanRequirementItems(section.requirementItems);
      return {
        id: section.id, position: index, name: section.name,
        requirementItems, planningNotes: '',
        todoClusters: todoClustersFromPlan(section, section.todoPlan),
      };
    });
  }
  function nextTodoNumber(sections) {
    const used = new Set();
    (sections || []).forEach((section) => {
      (((section.todoPlan || {}).categories) || []).forEach((category) => {
        (category.items || []).forEach((item) => {
          const number = Number(item.number) || 0;
          if (number > 0) used.add(number);
        });
      });
    });
    let next = 1;
    while (used.has(next)) next++;
    return next;
  }

  /* ── the hook ── */
  function useProjectPlanning(project, onAction) {
    const [draft, setDraft] = useState(() => projectPlanningDraft(project));
    const [activeId, setActiveId] = useState(() => { const d = projectPlanningDraft(project); return d[0] ? d[0].id : ''; });
    const [saving, setSaving] = useState(false);

    const saved = useMemo(() => projectPlanningDraft(project),
      [project.id, project.planningNotes, project.planningSections]);
    useEffect(() => {
      const next = projectPlanningDraft(project);
      setDraft(next);
      setActiveId((cur) => next.some((s) => s.id === cur) ? cur : (next[0] && next[0].id) || '');
    }, [project.id, project.planningNotes, project.planningSections]);

    const active = draft.find((s) => s.id === activeId) || draft[0];
    const dirty = useMemo(() => JSON.stringify(serializeSections(draft)) !== JSON.stringify(serializeSections(saved)), [draft, saved]);

    const patchSection = (id, patch) => setDraft((ss) => ss.map((s) => s.id === id ? { ...s, ...patch } : s));
    const patchActivePlan = (mutate) => patchSection(active.id, { todoPlan: { ...active.todoPlan, categories: mutate(active.todoPlan.categories) } });

    const renameCat = (catId, name) => patchActivePlan((cats) => cats.map((c) => c.id === catId ? { ...c, name } : c));
    // addTodo RETURNS the new id so the Board can open the edit drawer on the fresh card.
    const addTodo = (catId) => {
      const id = gid();
      const number = nextTodoNumber(draft);
      patchActivePlan((cats) => cats.map((c) => c.id === catId
        ? { ...c, items: [...c.items, { id, number, title: '', category: 'BacklogItem', done: false, status: 'To do', remarks: '', after: '', milestoneId: '', gitCommitLinks: [], messages: [], _isNew: true }] }
        : c));
      return id;
    };
    const patchTodo = (catId, todoId, patch) => patchActivePlan((cats) => cats.map((c) => c.id !== catId ? c : { ...c, items: c.items.map((it) => it.id === todoId ? { ...it, ...patch } : it) }));
    const removeTodo = (catId, todoId) => patchActivePlan((cats) => cats.map((c) => c.id !== catId ? c : { ...c, items: c.items.filter((it) => it.id !== todoId) }));
    const removeCluster = (catId) => patchActivePlan((cats) => {
      const cluster = cats.find((c) => c.id === catId);
      if (!cluster) return cats;
      if (cluster.items.length) return cats;
      return cats.filter((c) => c.id !== catId);
    });
    // List view move — keeps status, changes cluster only.
    const moveTodo = (fromCatId, todoId, toCatId) => patchActivePlan((cats) => {
      if (fromCatId === toCatId) return cats;
      const source = cats.find((c) => c.id === fromCatId);
      const todo = source && source.items.find((item) => item.id === todoId);
      if (!todo) return cats;
      return cats.map((c) => {
        if (c.id === fromCatId) return { ...c, items: c.items.filter((item) => item.id !== todoId) };
        if (c.id === toCatId) return { ...c, items: [...c.items, todo] };
        return c;
      });
    });
    // Board/drop drawer move — changes status (column), cluster (lane), and optional edited fields in one move.
    const moveTodoStatus = (fromCatId, todoId, toCatId, status, patch) => patchActivePlan((cats) => {
      const source = cats.find((c) => c.id === fromCatId);
      const todo = source && source.items.find((item) => item.id === todoId);
      if (!todo) return cats;
      const moved = { ...todo, ...(patch || {}), status, done: status === 'Done' };
      if (fromCatId === toCatId) return cats.map((c) => c.id !== toCatId ? c : { ...c, items: c.items.map((i) => i.id === todoId ? moved : i) });
      return cats.map((c) => {
        if (c.id === fromCatId) return { ...c, items: c.items.filter((i) => i.id !== todoId) };
        if (c.id === toCatId) return { ...c, items: [...c.items, moved] };
        return c;
      });
    });
    const addCluster = () => {
      patchActivePlan((cats) => [{ id: gid(), name: '', items: [] }, ...cats]);
    };
    const addFeature = () => {
      const next = window.AC.newProjectPlanningSection('New feature');
      const withPlan = { ...next, position: draft.length, todoPlan: sectionTodoPlan(next) };
      setDraft((d) => [...d, withPlan]);
      setActiveId(withPlan.id);
    };
    async function save() {
      setSaving(true);
      try {
        const serialized = serializeSections(draft);
        await onAction('updateProjectPlanning', {
          projectId: project.id,
          planningNotes: serialized[0] ? serialized[0].planningNotes : '',
          planningSections: serialized,
        });
      } finally { setSaving(false); }
    }
    const discard = () => setDraft(projectPlanningDraft(project));

    return {
      draft, active, activeId, setActiveId, dirty, saving, save, discard,
      patchSection, renameCat, addCluster, removeCluster,
      addTodo, patchTodo, removeTodo, moveTodo, moveTodoStatus, addFeature,
      get cats() { return active && active.todoPlan ? active.todoPlan.categories : []; },
    };
  }

  /* shared save bar — render once in the detail shell, visible on both tabs */
  function PlanningSaveBar({ planning }) {
    if (!planning.dirty) return null;
    return (
      <div className="pd-savebar">
        <Icon name="alert" size={13} /><span style={{ flex: 1 }}>Unsaved planning changes</span>
        <button className="btn sm ghost" onClick={planning.discard} disabled={planning.saving}>Discard</button>
        <button className="btn sm" onClick={planning.save} disabled={planning.saving}><Icon name="check" size={13} />{planning.saving ? 'Saving…' : 'Save planning'}</button>
      </div>
    );
  }

  Object.assign(window, { useProjectPlanning, PlanningSaveBar });
})();
