/* global React */
/*
  workspace-data.jsx — Play data via SharePoint file (T24-Plays.json).
  Uses Files.ReadWrite.All (delegated) — no Sites permissions needed.

  Drive: AWST24 site Documents library
  File:  /T24-Plays.json  (root of drive)
*/

// AWS Elevate Program SharePoint drive (Teams: AWS T24 group 21a55d48)
const SP_DRIVE_ID = "b!W-Fy9of3xUKBUCYYvYtORFxqX_Ml4-FNl7UVlPIFfxTXAi6pHB34S7n0EoXtUpeJ";
const SP_PLAYS_PATH = "AWS T24/T24-Plays.json";
const SP_SITE_BASE = "https://thebln24.sharepoint.com/sites/AWSElevateProgram";

// Web URL for a play's SharePoint folder. Persona is "CAIO" / "CIO" / etc.
// Falls back to the AWS T24 root if persona is missing.
function spPlayFolderUrl(persona) {
  const slug = persona ? `${persona} Elevate` : "";
  const rel = slug ? `/Shared Documents/AWS T24/${slug}` : `/Shared Documents/AWS T24`;
  return `${SP_SITE_BASE}${rel.split("/").map(encodeURIComponent).join("/")}`;
}

async function spGetUserToken(scope) {
  const scopes = scope
    ? [scope]
    : ["https://graph.microsoft.com/Files.ReadWrite.All"];
  if (!window.msalInstance) throw new Error("MSAL not initialized");
  const acct = window.msalInstance.getAllAccounts()[0];
  if (!acct) throw new Error("Not signed in");
  try {
    const r = await window.msalInstance.acquireTokenSilent({ scopes, account: acct });
    return r.accessToken;
  } catch {
    const r = await window.msalInstance.acquireTokenPopup({ scopes });
    return r.accessToken;
  }
}

async function readPlaysFile() {
  const token = await spGetUserToken();
  const url = `https://graph.microsoft.com/v1.0/drives/${SP_DRIVE_ID}/root:/${SP_PLAYS_PATH}:/content`;
  const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
  if (res.status === 404) return { plays: [], version: 1 };
  if (!res.ok) throw new Error(`Graph ${res.status}`);
  return await res.json();
}

async function writePlaysFile(data) {
  const token = await spGetUserToken();
  const url = `https://graph.microsoft.com/v1.0/drives/${SP_DRIVE_ID}/root:/${SP_PLAYS_PATH}:/content`;
  const res = await fetch(url, {
    method: "PUT",
    headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
    body: JSON.stringify(data, null, 2),
  });
  if (!res.ok) throw new Error(`Graph ${res.status}`);
  return await res.json();
}

function enrichPlay(raw) {
  // Parse stored JSON fields, derive display values
  let team = [];
  try { team = JSON.parse(raw.team || "[]"); } catch {}

  const stageIndex = typeof raw.stageIndex === "number" ? raw.stageIndex : 0;
  const stages = ["narrative", "arc", "storyboard", "deck"];

  let dueLabel = "TBD";
  let dueIn = 0;
  if (raw.due) {
    const d = new Date(raw.due);
    if (!isNaN(d)) {
      dueLabel = d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
      dueIn = Math.round((d - Date.now()) / 86400000);
    }
  }

  return {
    id: String(raw.id),
    spItemId: raw.id,
    slug: raw.persona ? `${raw.persona} Elevate` : String(raw.id),
    persona: raw.persona || "?",
    personaFull: raw.personaFull || raw.persona || "Unknown",
    industry: raw.industry || "Enterprise",
    title: raw.title || `${raw.personaFull || raw.persona} play`,
    stage: stages[stageIndex] || "narrative",
    stageIndex,
    progress: [25, 50, 75, 100][stageIndex] || 25,
    status: raw.status || "In progress",
    statusKind: raw.statusKind || "live",
    pipelineStartedAt: raw.pipelineStartedAt || null,
    due: dueLabel,
    dueIn,
    lead: raw.lead || "angie",
    team: team.length ? team : [raw.lead || "angie"],
    live: stageIndex < 3 ? [raw.lead || "angie"] : [],
    lastActivity: raw.lastActivity || "",
    lastActivityAt: "",
    cover: raw.cover || "linear-gradient(135deg,#1a3a5c 0%,#2d5a8c 100%)",
    accent: raw.accent || "#7aa7ff",
    tags: [raw.persona, "T24"].filter(Boolean),
    done: raw.done === true,
    stageOwners: ["angie", "angie", "stephen", "aws"],
    live_source: true,
    _raw: raw,
  };
}

function useLivePlays() {
  const [state, setState] = React.useState({ loading: true, plays: [], error: null });

  React.useEffect(() => {
    let cancelled = false;
    let timer = null;

    const tick = async () => {
      try {
        const data = await readPlaysFile();
        if (cancelled) return;
        const plays = (data.plays || []).map(enrichPlay);
        setState({ loading: false, plays, error: null });
        // Fast poll (15s) while any pipeline is mid-flight; idle poll (120s)
        // otherwise so we still catch new plays added by other users or the
        // cron without forcing a manual refresh.
        const anyGenerating = plays.some(p => p.statusKind === "generating");
        if (!cancelled) {
          timer = setTimeout(tick, anyGenerating ? 15000 : 120000);
        }
      } catch (e) {
        if (!cancelled) {
          setState({ loading: false, plays: [], error: e.message });
          // On transient failure (network blip, token refresh), keep trying
          // at the idle cadence instead of going silent forever.
          timer = setTimeout(tick, 120000);
        }
      }
    };

    tick();

    // Allow other code (delete/reset actions) to force an immediate refresh
    // without waiting for the next poll tick.
    const refreshHandler = () => {
      if (timer) clearTimeout(timer);
      tick();
    };
    window.addEventListener("t24:plays-changed", refreshHandler);

    return () => {
      cancelled = true;
      if (timer) clearTimeout(timer);
      window.removeEventListener("t24:plays-changed", refreshHandler);
    };
  }, []);

  return state;
}

async function spCreatePlay(fields) {
  const data = await readPlaysFile();
  const plays = data.plays || [];
  const newId = String(Date.now());
  const newRaw = {
    id: newId,
    title: fields.title || `${fields.personaFull || fields.persona} play`,
    persona: fields.persona || "",
    personaFull: fields.personaFull || fields.persona || "",
    industry: fields.industry || "Cross-industry · Fortune 500",
    due: fields.due || "",
    team: JSON.stringify(fields.team || ["angie", "stephen"]),
    lead: fields.lead || "angie",
    stageIndex: 0,
    status: fields.status || "Awaiting first draft",
    statusKind: fields.statusKind || "idle",
    pipelineStartedAt: fields.pipelineStartedAt || null,
    done: false,
    lastActivity: fields.lastActivity || "Play created",
    cover: fields.cover || "linear-gradient(135deg,#1a3a5c 0%,#2d5a8c 100%)",
    accent: fields.accent || "#7aa7ff",
  };
  plays.push(newRaw);
  await writePlaysFile({ ...data, plays });
  return enrichPlay(newRaw);
}

async function spAdvanceStage(playId, newStageIndex, extraFields = {}) {
  const data = await readPlaysFile();
  const plays = data.plays || [];
  const idx = plays.findIndex(p => String(p.id) === String(playId));
  if (idx === -1) throw new Error(`Play ${playId} not found`);

  // Build revision entry — always append, never overwrite history
  const { FeedbackFrom, FeedbackNotes, Status, ...otherFields } = extraFields;
  const revisionEntry = {
    id: `rev-${Date.now()}`,
    at: new Date().toISOString(),
    action: FeedbackNotes ? "send_back" : "approve",
    fromStageIndex: plays[idx].stageIndex,
    toStageIndex: newStageIndex,
    stageName: (["Narrative","Arc","Storyboard","Deck"][plays[idx].stageIndex] || "Unknown"),
    status: Status || (FeedbackNotes ? "Returned for revision" : "Approved"),
    from: FeedbackFrom || null,
    notes: FeedbackNotes || null,
  };

  const existingRevisions = Array.isArray(plays[idx].revisions) ? plays[idx].revisions : [];
  plays[idx] = {
    ...plays[idx],
    stageIndex: newStageIndex,
    revisions: [...existingRevisions, revisionEntry],
    // Keep last-feedback fields for quick surface reads
    lastFeedbackFrom: FeedbackFrom || plays[idx].lastFeedbackFrom || null,
    lastFeedbackNotes: FeedbackNotes || plays[idx].lastFeedbackNotes || null,
    lastStatus: Status || plays[idx].lastStatus || null,
    ...otherFields,
  };

  await writePlaysFile({ ...data, plays });
  return enrichPlay(plays[idx]);
}

async function spListStageFiles(playSlug, stage) {
  return window.spListFiles ? spListFiles(playSlug, stage) : [];
}

// ─── Reset / delete (testing + housekeeping) ─────────────────────────────
// Brian is testing personas — needs to close out a play and reset it without
// hand-editing T24-Plays.json. Reset keeps the play; Delete removes it.

async function spResetPlay(playId) {
  const data = await readPlaysFile();
  const plays = data.plays || [];
  const idx = plays.findIndex(p => String(p.id) === String(playId));
  if (idx === -1) throw new Error(`Play ${playId} not found`);

  plays[idx] = {
    ...plays[idx],
    stageIndex: 0,
    status: "Awaiting first draft",
    statusKind: "idle",
    pipelineStartedAt: null,
    revisions: [],
    lastFeedbackFrom: null,
    lastFeedbackNotes: null,
    lastStatus: null,
    lastActivity: "Play reset",
    done: false,
  };

  await writePlaysFile({ ...data, plays });
  return enrichPlay(plays[idx]);
}

async function spDeleteItemRecursive(driveId, itemId, token) {
  // Depth-first: list children, recurse, then DELETE this item.
  // Teams-backed SharePoint libraries reject DELETE on non-empty folders with 403,
  // so we have to empty them out first.
  const childrenUrl = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/children?$select=id,folder,name`;
  const listRes = await fetch(childrenUrl, { headers: { Authorization: `Bearer ${token}` } });
  if (listRes.ok) {
    const data = await listRes.json();
    for (const child of (data.value || [])) {
      await spDeleteItemRecursive(driveId, child.id, token);
    }
  }
  const delUrl = `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}`;
  const delRes = await fetch(delUrl, { method: "DELETE", headers: { Authorization: `Bearer ${token}` } });
  if (delRes.status === 204 || delRes.status === 404) return;
  const body = await delRes.text().catch(() => "");
  throw new Error(`Graph ${delRes.status} deleting item ${itemId}${body ? ` — ${body.slice(0, 200)}` : ""}`);
}

async function spDeletePlayFolder(playSlug) {
  // Resolve folder by path, then recursively delete its contents + itself.
  // Returns true on success, false if the folder was already gone (404).
  const token = await spGetUserToken();
  const folderPath = `AWS T24/${playSlug}`;
  const encoded = folderPath.split("/").map(encodeURIComponent).join("/");
  const metaUrl = `https://graph.microsoft.com/v1.0/drives/${SP_DRIVE_ID}/root:/${encoded}?$select=id`;
  const metaRes = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
  if (metaRes.status === 404) return false;
  if (!metaRes.ok) throw new Error(`Folder lookup failed: Graph ${metaRes.status}`);
  const meta = await metaRes.json();
  await spDeleteItemRecursive(SP_DRIVE_ID, meta.id, token);
  return true;
}

async function spDeletePlay(playId, { deleteFolder = false } = {}) {
  const data = await readPlaysFile();
  const plays = data.plays || [];
  const idx = plays.findIndex(p => String(p.id) === String(playId));
  if (idx === -1) throw new Error(`Play ${playId} not found`);

  const play = plays[idx];
  const removed = plays.splice(idx, 1)[0];
  await writePlaysFile({ ...data, plays });

  if (deleteFolder && play.persona) {
    // Folder convention: "<Persona> Elevate" — matches enrichPlay's slug.
    const slug = `${play.persona} Elevate`;
    try {
      await spDeletePlayFolder(slug);
    } catch (e) {
      // Surface the failure but don't undo the JSON delete — the entry is gone
      // either way, and the user can clean up the folder by hand if needed.
      console.warn(`[spDeletePlay] folder delete failed for ${slug}:`, e.message);
      throw new Error(`Play removed from list, but folder delete failed: ${e.message}`);
    }
  }
  return removed;
}

// ─── Pipeline notifications ───────────────────────────────────────────────
// Stored in localStorage. Inbox + bell badge + auto-toast read from here.
const NOTIF_KEY = "t24-pipeline-notifications";

function loadNotifications() {
  try { return JSON.parse(localStorage.getItem(NOTIF_KEY) || "[]"); } catch { return []; }
}
function saveNotifications(items) {
  try { localStorage.setItem(NOTIF_KEY, JSON.stringify(items)); } catch {}
}
function addNotification(notif) {
  const items = loadNotifications();
  items.unshift({ ...notif, id: `notif-${Date.now()}`, read: false, at: new Date().toISOString() });
  saveNotifications(items.slice(0, 50)); // cap at 50
  window.dispatchEvent(new CustomEvent("t24:notification", { detail: notif }));
}
function markAllRead() {
  saveNotifications(loadNotifications().map(n => ({ ...n, read: true })));
  window.dispatchEvent(new CustomEvent("t24:notifications-read"));
}

// Watches livePlays for generating → done transitions and fires notifications
function usePipelineNotifications(plays) {
  const prev = React.useRef({});
  React.useEffect(() => {
    plays.forEach(p => {
      const wasGenerating = prev.current[p.id] === "generating";
      const isDone = p.statusKind !== "generating";
      if (wasGenerating && isDone) {
        const actionMap = { "generate-arc": "Arc", "generate-storyboard": "Storyboard", "generate-deck": "Deck", "pipeline-request": "documents" };
        addNotification({
          type: "pipeline_complete",
          playId: p.id,
          playSlug: p.slug,
          persona: p.persona,
          title: `${p.persona} Elevate — pipeline complete`,
          body: `Your ${p.status && p.status.toLowerCase().includes("deck") ? "deck" : "documents"} are ready in the webapp.`,
          statusKind: p.statusKind,
        });
      }
      prev.current[p.id] = p.statusKind;
    });
  }, [plays]);
}

window.loadNotifications    = loadNotifications;
window.markAllRead          = markAllRead;
window.usePipelineNotifications = usePipelineNotifications;
window.useLivePlays = useLivePlays;
window.spGetUserToken = spGetUserToken;
window.spCreatePlay = spCreatePlay;
window.spAdvanceStage = spAdvanceStage;
window.spResetPlay = spResetPlay;
window.spDeletePlay = spDeletePlay;
window.spDeletePlayFolder = spDeletePlayFolder;
window.spPlayFolderUrl = spPlayFolderUrl;
window.spListStageFiles = spListStageFiles;
window.enrichPlay = enrichPlay;
window.SP_DRIVE_ID = SP_DRIVE_ID;
