/* === useTasks(householdId) — fourth per-table hook. Same pattern
   as useBrainNotes (parent + child tables joined in-memory).

   Returns flat-shape tasks (matching the blob's task shape with
   subtasks nested in an array) so call sites keep their existing
   mental model.

   Returns:
     {
       tasks,                                      // [{id, title, dueDate, ..., subtasks: [...]}]
       loading, error,

       // Task CRUD
       addTask(task),                              // includes subtasks if any
       updateTask(id, newTask),                    // full task object; we diff subtasks
       deleteTask(id),                             // soft-delete task + cascade soft-delete subtasks
       toggleTask(id, nextDone),                   // shortcut — UPDATE done + completedAt atomically

       // Subtask single-row ops — the collision-safe path for
       // two devices checking off different subtasks.
       updateSubtask(subtaskId, patch),

       // Cascade helpers
       cascadeGroupDelete(groupId),                // nullify group_id on all tasks
       clearCompletedOneOffs(),                    // bulk soft-delete done && !recurrence
     }
*/

function taskFromRow(row, subtasks) {
  const t = {
    id: row.id,
    title: row.title || '',
    dueDate: row.due_date || null,
    memberId: row.member_id || null,
    groupId: row.group_id || null,
    note: row.note || '',
    done: !!row.done,
    completedAt: row.completed_at || null,
    subtasks: (subtasks || []).map(subtaskFromRow),
  };
  if (row.priority) t.priority = row.priority;
  if (row.recurrence_type) t.recurrence = { type: row.recurrence_type };
  return t;
}

function taskToRow(task, householdId) {
  return {
    id: task.id,
    household_id: householdId,
    title: task.title || '',
    due_date: task.dueDate || null,
    member_id: task.memberId || null,
    group_id: task.groupId || null,
    note: task.note || null,
    priority: task.priority || null,
    done: !!task.done,
    completed_at: task.completedAt || null,
    recurrence_type: task.recurrence?.type || null,
  };
}

function subtaskFromRow(row) {
  return {
    id: row.id,
    title: row.title || '',
    memberId: row.member_id || null,
    done: !!row.done,
    position: row.position,
  };
}

function subtaskToRow(sub, taskId, householdId, position) {
  return {
    id: sub.id,
    household_id: householdId,
    task_id: taskId,
    title: sub.title || '',
    member_id: sub.memberId || null,
    done: !!sub.done,
    position: typeof position === 'number' ? position : (sub.position || 0),
  };
}

function newTaskId()    { return 't'  + Date.now().toString(36) + Math.random().toString(36).slice(2, 5); }
function newSubtaskId() { return 'st' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5); }

function useTasks(householdId) {
  const [tasks, setTasks]       = React.useState([]);
  const [loading, setLoading]   = React.useState(true);
  const [error, setError]       = React.useState(null);

  const pendingIds = React.useRef(new Set());

  const applySubtaskChange = (subRow) => {
    setTasks(prev => prev.map(t => {
      if (t.id !== subRow.task_id) return t;
      const existing = t.subtasks || [];
      if (subRow.deleted_at) {
        return { ...t, subtasks: existing.filter(s => s.id !== subRow.id) };
      }
      const incoming = subtaskFromRow(subRow);
      const i = existing.findIndex(s => s.id === subRow.id);
      const next = i < 0
        ? [...existing, incoming]
        : existing.map(s => s.id === subRow.id ? incoming : s);
      next.sort((a, b) => (a.position || 0) - (b.position || 0));
      return { ...t, subtasks: next };
    }));
  };

  // Initial load: tasks + subtasks, then join.
  React.useEffect(() => {
    if (!householdId) return;
    let cancelled = false;
    setLoading(true);
    (async () => {
      const [tasksRes, subRes] = await Promise.all([
        supabaseClient.from('tasks')
          .select('*')
          .eq('household_id', householdId)
          .is('deleted_at', null)
          .order('due_date', { ascending: true, nullsFirst: false }),
        supabaseClient.from('task_subtasks')
          .select('*')
          .eq('household_id', householdId)
          .is('deleted_at', null)
          .order('position', { ascending: true }),
      ]);
      if (cancelled) return;
      if (tasksRes.error) { setError(tasksRes.error.message); setLoading(false); return; }
      if (subRes.error)   { setError(subRes.error.message);   setLoading(false); return; }
      const subsByTask = new Map();
      (subRes.data || []).forEach(row => {
        const arr = subsByTask.get(row.task_id) || [];
        arr.push(row);
        subsByTask.set(row.task_id, arr);
      });
      const joined = (tasksRes.data || []).map(row => taskFromRow(row, subsByTask.get(row.id)));
      setTasks(joined);
      setError(null);
      setLoading(false);
    })();
    return () => { cancelled = true; };
  }, [householdId]);

  // Realtime — tasks INSERT/UPDATE + subtasks INSERT/UPDATE.
  React.useEffect(() => {
    if (!householdId) return;
    const channel = supabaseClient
      .channel(`tasks:${householdId}`)
      .on('postgres_changes',
          { event: 'INSERT', schema: 'public', table: 'tasks', filter: `household_id=eq.${householdId}` },
          (payload) => {
            const row = payload.new;
            if (pendingIds.current.has(row.id)) { pendingIds.current.delete(row.id); return; }
            setTasks(prev => prev.some(t => t.id === row.id) ? prev : [taskFromRow(row, []), ...prev]);
          })
      .on('postgres_changes',
          { event: 'UPDATE', schema: 'public', table: 'tasks', filter: `household_id=eq.${householdId}` },
          (payload) => {
            const row = payload.new;
            if (pendingIds.current.has(row.id)) { pendingIds.current.delete(row.id); return; }
            setTasks(prev => {
              if (row.deleted_at) return prev.filter(t => t.id !== row.id);
              const existing = prev.find(t => t.id === row.id);
              const subs = existing?.subtasks || [];
              return prev.map(t => t.id === row.id ? taskFromRow(row, subs) : t);
            });
          })
      .on('postgres_changes',
          { event: 'INSERT', schema: 'public', table: 'task_subtasks', filter: `household_id=eq.${householdId}` },
          (payload) => {
            if (pendingIds.current.has(payload.new.id)) { pendingIds.current.delete(payload.new.id); return; }
            applySubtaskChange(payload.new);
          })
      .on('postgres_changes',
          { event: 'UPDATE', schema: 'public', table: 'task_subtasks', filter: `household_id=eq.${householdId}` },
          (payload) => {
            if (pendingIds.current.has(payload.new.id)) { pendingIds.current.delete(payload.new.id); return; }
            applySubtaskChange(payload.new);
          })
      .subscribe();
    return () => { supabaseClient.removeChannel(channel); };
  }, [householdId]);

  // ----- Mutations -----

  const addTask = React.useCallback(async (task) => {
    if (!householdId) throw new Error('No household');
    const id = task.id || newTaskId();
    const full = { ...task, id, subtasks: task.subtasks || [] };
    pendingIds.current.add(id);
    setTasks(prev => [full, ...prev]);
    const { error } = await supabaseClient.from('tasks').insert(taskToRow(full, householdId));
    if (error) {
      pendingIds.current.delete(id);
      setTasks(prev => prev.filter(t => t.id !== id));
      setError(error.message);
      throw error;
    }
    if (full.subtasks.length) {
      const subRows = full.subtasks.map((s, idx) => {
        const sid = s.id || newSubtaskId();
        pendingIds.current.add(sid);
        return subtaskToRow({ ...s, id: sid }, id, householdId, idx);
      });
      const { error: subErr } = await supabaseClient.from('task_subtasks').insert(subRows);
      if (subErr) {
        subRows.forEach(r => pendingIds.current.delete(r.id));
        setError('Task saved but subtasks failed: ' + subErr.message);
      }
    }
    setError(null);
    return full;
  }, [householdId]);

  // Reconcile child subtasks against a new desired list. Used by
  // updateTask for TaskModal edits — diff by id: new INSERT,
  // changed UPDATE (title/done/position/memberId), missing soft-delete.
  const reconcileSubtasks = async (taskId, prevSubs, nextSubs) => {
    const prevById = new Map((prevSubs || []).map(s => [s.id, s]));
    const nextById = new Map((nextSubs || []).map(s => [s.id, s]));
    const toInsert = [];
    const toUpdate = [];
    const toDelete = [];
    (nextSubs || []).forEach((s, idx) => {
      const prev = prevById.get(s.id);
      if (!prev) {
        const sid = s.id || newSubtaskId();
        toInsert.push(subtaskToRow({ ...s, id: sid }, taskId, householdId, idx));
      } else if (prev.title !== s.title || prev.done !== s.done
                 || prev.memberId !== (s.memberId || null) || prev.position !== idx) {
        toUpdate.push({
          id: s.id, title: s.title || '', done: !!s.done,
          member_id: s.memberId || null, position: idx,
        });
      }
    });
    (prevSubs || []).forEach(p => { if (!nextById.has(p.id)) toDelete.push(p.id); });
    const ops = [];
    if (toInsert.length) {
      toInsert.forEach(r => pendingIds.current.add(r.id));
      ops.push(supabaseClient.from('task_subtasks').insert(toInsert));
    }
    toUpdate.forEach(p => {
      pendingIds.current.add(p.id);
      ops.push(supabaseClient.from('task_subtasks')
        .update({ title: p.title, done: p.done, member_id: p.member_id, position: p.position })
        .eq('id', p.id));
    });
    toDelete.forEach(id => {
      pendingIds.current.add(id);
      ops.push(supabaseClient.from('task_subtasks')
        .update({ deleted_at: new Date().toISOString() })
        .eq('id', id));
    });
    if (!ops.length) return;
    const results = await Promise.all(ops);
    const errs = results.filter(r => r.error);
    if (errs.length) throw new Error(errs.map(e => e.error.message).join('; '));
  };

  const updateTask = React.useCallback(async (id, newTask) => {
    if (!householdId) throw new Error('No household');
    const prev = tasks.find(t => t.id === id) || null;
    if (prev) {
      pendingIds.current.add(id);
      setTasks(p => p.map(t => t.id === id ? { ...newTask, id } : t));
    }
    const row = taskToRow({ ...newTask, id }, householdId);
    const { household_id: _hid, ...patchRow } = row;
    const { error } = await supabaseClient.from('tasks').update(patchRow).eq('id', id);
    if (error) {
      if (prev) {
        pendingIds.current.delete(id);
        setTasks(p => p.map(t => t.id === id ? prev : t));
      }
      setError(error.message);
      throw error;
    }
    // Reconcile subtasks if the caller included them.
    if (Array.isArray(newTask.subtasks)) {
      try {
        await reconcileSubtasks(id, prev?.subtasks || [], newTask.subtasks);
      } catch (subErr) {
        setError('Task updated but subtasks failed: ' + subErr.message);
      }
    }
    setError(null);
  }, [householdId, tasks]);

  const deleteTask = React.useCallback(async (id) => {
    if (!householdId) throw new Error('No household');
    const prev = tasks.find(t => t.id === id) || null;
    if (prev) {
      pendingIds.current.add(id);
      setTasks(p => p.filter(t => t.id !== id));
    }
    const now = new Date().toISOString();
    const [tRes, sRes] = await Promise.all([
      supabaseClient.from('tasks').update({ deleted_at: now }).eq('id', id),
      supabaseClient.from('task_subtasks').update({ deleted_at: now }).eq('task_id', id),
    ]);
    if (tRes.error) {
      if (prev) {
        pendingIds.current.delete(id);
        setTasks(p => [prev, ...p]);
      }
      setError(tRes.error.message);
      throw tRes.error;
    }
    if (sRes.error) setError('Task deleted but subtask soft-delete failed: ' + sRes.error.message);
    else setError(null);
  }, [householdId, tasks]);

  // toggleTask: single UPDATE to both done + completed_at so the
  // two fields stay consistent on the server even if the realtime
  // echo lands before our local optimistic state settles.
  const toggleTask = React.useCallback(async (id, nextDone) => {
    if (!householdId) throw new Error('No household');
    const prev = tasks.find(t => t.id === id) || null;
    const now = new Date().toISOString();
    const completedAt = nextDone ? now : null;
    if (prev) {
      pendingIds.current.add(id);
      setTasks(p => p.map(t => t.id === id ? { ...t, done: nextDone, completedAt } : t));
    }
    const { error } = await supabaseClient.from('tasks')
      .update({ done: nextDone, completed_at: completedAt })
      .eq('id', id);
    if (error) {
      if (prev) {
        pendingIds.current.delete(id);
        setTasks(p => p.map(t => t.id === id ? prev : t));
      }
      setError(error.message);
      throw error;
    }
    setError(null);
  }, [householdId, tasks]);

  // updateSubtask — single-row UPDATE. Collision-safe path for
  // two devices checking off different subtasks of the same task.
  const updateSubtask = React.useCallback(async (subtaskId, patch) => {
    if (!householdId) throw new Error('No household');
    let prevSnapshot = null;
    setTasks(prev => prev.map(t => {
      if (!t.subtasks?.length) return t;
      const i = t.subtasks.findIndex(s => s.id === subtaskId);
      if (i < 0) return t;
      prevSnapshot = t.subtasks[i];
      const next = [...t.subtasks];
      next[i] = { ...next[i], ...patch };
      return { ...t, subtasks: next };
    }));
    if (prevSnapshot) pendingIds.current.add(subtaskId);
    const columnPatch = {};
    if ('title'    in patch) columnPatch.title     = patch.title || '';
    if ('done'     in patch) columnPatch.done      = !!patch.done;
    if ('memberId' in patch) columnPatch.member_id = patch.memberId || null;
    if ('position' in patch) columnPatch.position  = patch.position;
    const { error } = await supabaseClient.from('task_subtasks').update(columnPatch).eq('id', subtaskId);
    if (error) {
      if (prevSnapshot) {
        pendingIds.current.delete(subtaskId);
        setTasks(prev => prev.map(t => {
          if (!t.subtasks?.length) return t;
          const i = t.subtasks.findIndex(s => s.id === subtaskId);
          if (i < 0) return t;
          const next = [...t.subtasks];
          next[i] = prevSnapshot;
          return { ...t, subtasks: next };
        }));
      }
      setError(error.message);
      throw error;
    }
    setError(null);
  }, [householdId]);

  // cascadeGroupDelete — when a taskGroup is deleted, every task
  // referencing it gets group_id nulled. Server-side bulk UPDATE
  // covers legacy rows not in local state.
  const cascadeGroupDelete = React.useCallback(async (groupId) => {
    if (!householdId) return null;
    try {
      const { error } = await supabaseClient.from('tasks')
        .update({ group_id: null })
        .eq('household_id', householdId)
        .eq('group_id', groupId);
      if (error) throw error;
      return true;
    } catch (err) {
      console.error('[useTasks] cascadeGroupDelete failed:', err);
      throw err;
    }
  }, [householdId]);

  // clearCompletedOneOffs — mirror of ledger-app.jsx's choreCleanup
  // effect. Bulk soft-deletes every done one-off task; recurring
  // tasks (recurrence_type IS NOT NULL) survive untouched.
  const clearCompletedOneOffs = React.useCallback(async () => {
    if (!householdId) return null;
    try {
      const { error } = await supabaseClient.from('tasks')
        .update({ deleted_at: new Date().toISOString() })
        .eq('household_id', householdId)
        .eq('done', true)
        .is('recurrence_type', null)
        .is('deleted_at', null);
      if (error) throw error;
      return true;
    } catch (err) {
      console.error('[useTasks] clearCompletedOneOffs failed:', err);
      throw err;
    }
  }, [householdId]);

  return {
    tasks, loading, error,
    addTask, updateTask, deleteTask, toggleTask,
    updateSubtask,
    cascadeGroupDelete, clearCompletedOneOffs,
  };
}

Object.assign(window, { useTasks });
