// ============ TaskQuest — data & logic ============
// Clean slate: no demo users, no seeded tasks. ROLES/PROJECTS are populated at
// runtime — from Supabase in LIVE mode, or in-memory in OFFLINE preview mode.

const ROLES = {};        // id -> profile  (filled by applyProfiles)
const CONSULTANTS = [];  // profile ids
const MANAGERS = [];     // profile ids

// palette for newly-created users / projects
const PALETTE = ["#5E6AD2", "#16a394", "#e0902b", "#eb5757", "#8b7cf6", "#4ea7fc", "#d4569b", "#2bb673", "#e8633a", "#0d9488"];

// Configurable scoring system (editable by System Admin)
const CONFIG = {
  onTimePoints: 10,
  latePoints: 0,
  bonusTiers: [5, 10, 20],
};

function roleLabelFor(type) {
  return type === "pm" ? "Project Manager" : type === "dm" ? "Delivery Manager" : type === "admin" ? "System Admin" : "Consultant";
}
function makeInitials(name) {
  const parts = name.trim().split(/\s+/);
  if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}

// Starter projects (also seeded in supabase/schema.sql so LIVE & OFFLINE match).
const PROJECTS = {
  alpha: { key: "alpha", name: "Project Alpha", color: "#5E6AD2" },
  beta:  { key: "beta",  name: "Project Beta",  color: "#16a394" },
  gamma: { key: "gamma", name: "Project Gamma", color: "#e0902b" },
};

let _projSeq = 0;
function addProject({ name, color }) {
  _projSeq += 1;
  const key = "p" + _projSeq + "_" + name.trim().toLowerCase().replace(/[^a-z]/g, "").slice(0, 6);
  PROJECTS[key] = { key, name: name.trim(), color: color || PALETTE[_projSeq % PALETTE.length] };
  return key;
}

const COLUMNS = [
  { key: "happy",   name: "Happy Path",   color: "#4cb782", desc: "On track" },
  { key: "unhappy", name: "Needs attention", color: "#f2c94c", desc: "Date change pending" },
  { key: "onhold",  name: "On Hold",      color: "#878b94", desc: "Blocked / paused" },
  { key: "done",    name: "Done",         color: "#8b7cf6", desc: "Completed" },
];

// Task sub-categories — the kind of work a task represents.
const SUBCATEGORIES = [
  { key: "code",    name: "Code",          color: "#5E6AD2" },
  { key: "ui",      name: "UI",            color: "#4ea7fc" },
  { key: "design",  name: "Design",        color: "#8b7cf6" },
  { key: "docs",    name: "Documentation", color: "#16a394" },
  { key: "qa",      name: "QA / Testing",  color: "#e0902b" },
  { key: "review",  name: "Review",        color: "#cf6ad9" },
  { key: "other",   name: "Other",         color: "#878b94" },
];

// ============ Deliverables ============
// A Deliverable is a high-level unit of work the Delivery Manager creates and
// assigns to a Project Manager (a backlog). The PM breaks it down into tasks;
// the deliverable's status & progress are derived from those child tasks.
const DELIVERABLES = [];   // filled at runtime (seed in OFFLINE mode)

const DLV_STATUS = {
  assigned:   { key: "assigned",   name: "Assigned",    color: "#4ea7fc", desc: "Awaiting breakdown" },
  inprogress: { key: "inprogress", name: "In Progress", color: "#f2c94c", desc: "Being delivered" },
  delivered:  { key: "delivered",  name: "Delivered",   color: "#4cb782", desc: "Ready for sign-off" },
  accepted:   { key: "accepted",   name: "Accepted",    color: "#8b7cf6", desc: "Signed off by Delivery" },
};
const DLV_STATUS_ORDER = ["assigned", "inprogress", "delivered", "accepted"];

const PRIORITY = {
  high: { key: "high", name: "High",   color: "#eb5757" },
  med:  { key: "med",  name: "Medium", color: "#e0902b" },
  low:  { key: "low",  name: "Low",    color: "#878b94" },
};

let _did = 100;
function mkDeliverable(o) {
  _did += 1;
  return Object.assign({
    id: "DLV-" + _did,
    priority: "med",
    desc: "",
    accepted: false,       // DM sign-off once all child tasks are done
    comments: [],
  }, o);
}

function deliverableTasks(d, tasks) {
  return tasks.filter(t => t.deliverable === d.id);
}
// Derive a deliverable's live status + progress from its child tasks.
function computeDeliverable(d, tasks) {
  const kids = deliverableTasks(d, tasks);
  const total = kids.length;
  const done = kids.filter(t => t.status === "done").length;
  const overdue = kids.some(t => t.status !== "done" && daysUntil(t.due) < 0);
  let status;
  if (d.accepted) status = "accepted";
  else if (total === 0) status = "assigned";
  else if (done === total) status = "delivered";
  else status = "inprogress";
  const pct = total ? Math.round((done / total) * 100) : 0;
  return { kids, total, done, pct, overdue, status, meta: DLV_STATUS[status] };
}
function applyDeliverables(list) {
  DELIVERABLES.length = 0;
  (list || []).forEach(d => DELIVERABLES.push(d));
}

// date helpers — "today" is the real current date (so past-date blocking is
// correct and demo seed offsets always render relative to today). Normalized to
// local midnight for clean date-only comparisons.
const TODAY = (function () { const n = new Date(); return new Date(n.getFullYear(), n.getMonth(), n.getDate()); })();
function isoFromOffset(days) {
  const d = new Date(TODAY); d.setDate(d.getDate() + days);
  return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
}
function parseISO(s) { const [y, m, d] = s.split("-").map(Number); return new Date(y, m - 1, d); }
function fmtDate(iso) {
  if (!iso) return "—";
  const d = parseISO(iso);
  return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}
function fmtDateLong(iso) {
  if (!iso) return "—";
  return parseISO(iso).toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", year: "numeric" });
}
function daysUntil(iso) { return Math.round((parseISO(iso) - TODAY) / 86400000); }
function relDue(iso) {
  const n = daysUntil(iso);
  if (n === 0) return "Due today";
  if (n === 1) return "Due tomorrow";
  if (n === -1) return "1 day overdue";
  if (n < 0) return `${-n} days overdue`;
  if (n <= 7) return `Due in ${n} days`;
  return "Due " + fmtDate(iso);
}

let _tid = 100;
function mkTask(o) {
  _tid += 1;
  return Object.assign({
    id: "TQ-" + _tid + "-" + Math.random().toString(36).slice(2, 6),
    comments: [],
    deliverable: null,     // parent Deliverable id (set when a PM breaks one down)
    description: "",       // longer task description (optional)
    subcategory: null,     // SUBCATEGORIES key (code/ui/design/docs/qa/review/other)
    request: null,         // { newDate, reason, comments, status:'pending'|'approved'|'rejected', approvedDate }
    completion: null,      // { onTime:bool, status:'pending'|'approved', comments }
  }, o);
}

// ============ Scoring & leaderboard ============
function computeScores(tasks, bonus) {
  const cfg = window.TQ ? window.TQ.CONFIG : CONFIG;
  const stats = {};
  CONSULTANTS.forEach(c => { stats[c] = { key: c, base: 0, bonus: 0, total: 0, assigned: 0, ontime: 0, completed: 0, perf: 0 }; });
  tasks.forEach(t => {
    if (!stats[t.assignee]) return;
    const s = stats[t.assignee];
    // On Hold work is blocked/paused — it doesn't count toward the assigned workload.
    // "Assigned" = Happy Path + Needs Attention + Done.
    if (t.status === "onhold") return;
    s.assigned += 1;
    if (t.status === "done") {
      s.completed += 1;
      if (t.completion && t.completion.status === "approved") {
        if (t.completion.onTime) { s.ontime += 1; s.base += cfg.onTimePoints; }
        else { s.base += cfg.latePoints; }
      }
    }
  });
  bonus.forEach(b => { if (stats[b.to]) stats[b.to].bonus += b.pts; });
  Object.values(stats).forEach(s => {
    s.total = s.base + s.bonus;
    // Average points per completed task: (on-time/late points + bonus) ÷ tasks completed.
    s.avg = s.completed ? s.total / s.completed : 0;
    // Performance = tasks completed (Done) ÷ tasks assigned (Happy Path + Needs Attention + Done).
    s.perf = s.assigned ? Math.round((s.completed / s.assigned) * 100) : 0;
  });
  return stats;
}

function leaderboard(tasks, bonus, projectKey) {
  const scoped = projectKey === "all" ? tasks : tasks.filter(t => t.project === projectKey);
  const scopedBonus = projectKey === "all" ? bonus : bonus.filter(b => b.project === projectKey);
  const stats = computeScores(scoped, scopedBonus);
  const arr = Object.values(stats);
  arr.sort((a, b) => b.avg - a.avg || b.perf - a.perf || a.key.localeCompare(b.key));
  arr.forEach((s, i) => { s.rank = i + 1; });
  return arr;
}

// ============ Live-data adapters ============
// Mutate the SAME ROLES/PROJECTS/CONFIG objects in place so references that
// components destructured at load time stay valid.
function applyProfiles(map) {
  Object.keys(ROLES).forEach(k => delete ROLES[k]);
  Object.assign(ROLES, map);
  CONSULTANTS.length = 0; MANAGERS.length = 0;
  Object.values(map).forEach(r => {
    if (r.type === "consultant") CONSULTANTS.push(r.key);
    else if (r.type === "pm" || r.type === "dm") MANAGERS.push(r.key);
  });
}
function applyProjects(map) {
  Object.keys(PROJECTS).forEach(k => delete PROJECTS[k]);
  Object.assign(PROJECTS, map);
}
function applyConfig(cfg) { if (cfg) Object.assign(CONFIG, cfg); }

// A notification is visible to a user if it targets their id, their role type,
// or the 'type:<type>' token the database stores.
function notifVisible(n, meId, meType) {
  const f = n.forRoles || [];
  return f.indexOf(meId) >= 0 || f.indexOf(meType) >= 0 || f.indexOf("type:" + meType) >= 0;
}

// A profile's project membership as an array of valid project keys. Prefers the
// multi-value `projects` array, falls back to the legacy single `project` field,
// and drops any keys that no longer exist.
function projectsOf(r) {
  if (!r) return [];
  const list = Array.isArray(r.projects) && r.projects.length
    ? r.projects
    : (r.project ? [r.project] : []);
  return list.filter(k => PROJECTS[k]);
}

window.TQ = {
  ROLES, CONSULTANTS, MANAGERS, PROJECTS, COLUMNS, SUBCATEGORIES, TODAY, CONFIG, PALETTE,
  DELIVERABLES, DLV_STATUS, DLV_STATUS_ORDER, PRIORITY,
  isoFromOffset, parseISO, fmtDate, fmtDateLong, daysUntil, relDue,
  mkTask, mkDeliverable, deliverableTasks, computeDeliverable, computeScores, leaderboard,
  addProject, roleLabelFor, makeInitials, projectsOf,
  applyProfiles, applyProjects, applyConfig, applyDeliverables, notifVisible,
};
