"use client";

import { useState, useEffect, useCallback } from "react";
import Link from "next/link";
import { useBrand } from "@/context/brand-context";
import {
  Bell,
  AlertCircle,
  Zap,
  TrendingUp,
  DollarSign,
  Bot,
  ListChecks,
  CheckCircle2,
  Eye,
  ChevronRight,
  ClipboardPlus,
  Clock,
  Filter,
  ArrowUpRight,
  ArrowDownRight,
  ExternalLink,
  Pin,
  X,
  Sparkles,
} from "lucide-react";
import { MarkdownContent, stripMarkdown } from "@/components/shared/MarkdownContent";

// ─── Types ───────────────────────────────────────────────────────────────────

type SignalCategory = "issue" | "win" | "opportunity" | "revenue" | "ai" | "execution";
type SignalPriority = "critical" | "high" | "medium" | "low";
type SignalStatus = "new" | "viewed" | "actioned";
type FilterKey = "all" | SignalCategory;

interface Signal {
  id: string;
  title: string;
  description: string;
  category: SignalCategory;
  priority: SignalPriority;
  status: SignalStatus;
  timestamp: string;
  relatedPage?: string;
  relatedChannel?: string;
  source: string;
  issueIds?: string[];
  /** Derived revenue-impact bucket. Only set when the signal is
   *  revenue-relevant — the UI only renders the badge when this
   *  field is truthy, so every tile doesn't sport a useless "Low
   *  Revenue Impact" pill. Derivation lives server-side in
   *  src/lib/services/signals.ts → deriveRevenueImpact(). */
  revenueImpact?: "high" | "medium" | "low";
  /** AI-generated next step — set by signal_ai_* rows. */
  suggestedAction?: string;
  /** Competitor name for signal_ai_competitor rows. */
  competitor?: string;
}

// ─── Config ──────────────────────────────────────────────────────────────────

const CATEGORY_CFG: Record<SignalCategory, { label: string; icon: typeof AlertCircle; color: string; bg: string; border: string; dot: string }> = {
  issue:       { label: "Issue",       icon: AlertCircle, color: "text-red-700",    bg: "bg-red-50",    border: "border-red-200",    dot: "bg-red-500" },
  win:         { label: "Win",         icon: TrendingUp,  color: "text-green-700",  bg: "bg-green-50",  border: "border-green-200",  dot: "bg-green-500" },
  opportunity: { label: "Opportunity", icon: Zap,         color: "text-blue-700",   bg: "bg-blue-50",   border: "border-blue-200",   dot: "bg-blue-500" },
  revenue:     { label: "Revenue",     icon: DollarSign,  color: "text-emerald-700",bg: "bg-emerald-50",border: "border-emerald-200",dot: "bg-emerald-500" },
  ai:          { label: "AI Signal",   icon: Bot,         color: "text-violet-700", bg: "bg-violet-50", border: "border-violet-200", dot: "bg-violet-500" },
  execution:   { label: "Execution",   icon: ListChecks,  color: "text-amber-700",  bg: "bg-amber-50",  border: "border-amber-200",  dot: "bg-amber-500" },
};

const PRIORITY_CFG: Record<SignalPriority, { label: string; color: string; bg: string; dot: string }> = {
  critical: { label: "Critical", color: "text-red-700",    bg: "bg-red-50 border-red-200",    dot: "bg-red-500" },
  high:     { label: "High",     color: "text-orange-700", bg: "bg-orange-50 border-orange-200", dot: "bg-orange-400" },
  medium:   { label: "Medium",   color: "text-amber-700",  bg: "bg-amber-50 border-amber-200", dot: "bg-amber-300" },
  low:      { label: "Low",      color: "text-slate-500",  bg: "bg-slate-50 border-slate-200", dot: "bg-slate-300" },
};

const STATUS_CFG: Record<SignalStatus, { label: string; color: string; border: string; icon: typeof CheckCircle2 }> = {
  new:      { label: "New",      color: "text-blue-600",  border: "border-blue-200",  icon: Bell },
  viewed:   { label: "Viewed",   color: "text-slate-400", border: "border-slate-200", icon: Eye },
  actioned: { label: "Actioned", color: "text-green-600", border: "border-green-200", icon: CheckCircle2 },
};

const FILTERS: { key: FilterKey; label: string; icon: typeof Filter }[] = [
  { key: "all",         label: "All Signals",  icon: Filter },
  { key: "issue",       label: "Issues",       icon: AlertCircle },
  { key: "win",         label: "Wins",         icon: TrendingUp },
  { key: "opportunity", label: "Opportunities",icon: Zap },
  { key: "revenue",     label: "Revenue",      icon: DollarSign },
  { key: "ai",          label: "AI Signals",   icon: Bot },
  { key: "execution",   label: "Execution",    icon: ListChecks },
];

// ─── Page ────────────────────────────────────────────────────────────────────

export default function SignalsCenterPage() {
  const { brand } = useBrand();

  const [filter, setFilter] = useState<FilterKey>("all");
  const [signals, setSignals] = useState<Signal[]>([]);
  const [taskFeedback, setTaskFeedback] = useState<Record<string, string>>({});
  const [loading, setLoading] = useState(false);
  // Surfaces load failures instead of leaving the page on an empty
  // state — previously the catch swallowed errors silently and the
  // user saw "No signals detected yet" copy indistinguishable from
  // a brand with a genuinely empty signal list.
  const [loadError, setLoadError] = useState<string | null>(null);
  const [dismissed, setDismissed] = useState<Set<string>>(new Set());
  const [pinned, setPinned] = useState<Set<string>>(new Set());
  // Accordion: the id of the currently-expanded signal card, or null
  // when every card is collapsed. Clicking "Details" on one tile
  // closes any other open tile — prevents the list from growing
  // unmanageably tall when the user is triaging.
  const [expandedId, setExpandedId] = useState<string | null>(null);

  // AI recommendations cache, keyed by signal id. Loaded on demand when
  // a signal is expanded. Loading state separate so we can show a
  // skeleton while the LLM call is in flight.
  interface AiRec {
    priority: "critical" | "high" | "medium" | "low";
    title: string;
    description: string;
    suggestedFix?: string;
    fixType?: "meta" | "h1" | "schema" | "canonical" | "content" | "code";
  }
  const [recsBySignal, setRecsBySignal] = useState<Record<string, { recommendations: AiRec[]; impactEstimate?: string; loading: boolean; error?: string }>>({});

  const loadRecsForSignal = useCallback(async (signal: Signal) => {
    if (!brand?.id) return;
    if (recsBySignal[signal.id]?.recommendations.length) return; // already loaded
    setRecsBySignal((prev) => ({ ...prev, [signal.id]: { recommendations: [], loading: true } }));
    try {
      const res = await fetch("/api/signals/recommendations", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          signalId: signal.id,
          brandId: brand.id,
          signal: {
            title: signal.title,
            description: signal.description,
            source: signal.source,
            category: signal.category,
            priority: signal.priority,
            relatedPage: signal.relatedPage,
            suggestedAction: signal.suggestedAction,
          },
        }),
      });
      if (!res.ok) {
        setRecsBySignal((prev) => ({ ...prev, [signal.id]: { recommendations: [], loading: false, error: `HTTP ${res.status}` } }));
        return;
      }
      const data = await res.json() as { recommendations: AiRec[]; impactEstimate?: string };
      setRecsBySignal((prev) => ({
        ...prev,
        [signal.id]: { recommendations: data.recommendations ?? [], impactEstimate: data.impactEstimate, loading: false },
      }));
    } catch (err) {
      setRecsBySignal((prev) => ({ ...prev, [signal.id]: { recommendations: [], loading: false, error: err instanceof Error ? err.message : "Network error" } }));
    }
  }, [brand?.id, recsBySignal]);

  const loadSignals = useCallback(async () => {
    // Wait for brand context to resolve — the placeholder brand has
    // id="". Firing the fetch with an empty brandId produces
    // /api/signals?brandId= which the server treats as a malformed
    // request and we got a transient 400 + blank signal list on
    // first render.
    if (!brand?.id) {
      setLoading(false);
      return;
    }
    setLoading(true);
    try {
      const res = await fetch(`/api/signals?brandId=${encodeURIComponent(brand.id)}`, { cache: "no-store" });
      if (!res.ok) {
        // Previously this threw into the catch, which then called
        // setSignals([]) and cleared the list silently. Now we keep
        // the current list visible and surface a banner so the user
        // can distinguish "empty" from "stale because the API just
        // returned 500".
        setLoadError(`Couldn't refresh signals (HTTP ${res.status}). Showing last known state.`);
        setLoading(false);
        return;
      }
      const data = await res.json();

      if (Array.isArray(data)) {
        const mapped: Signal[] = data.map((s: Record<string, unknown>) => ({
          id: (s.id as string) || `sig_${Math.random().toString(36).slice(2)}`,
          title: s.title as string,
          description: s.description as string,
          category: s.category as SignalCategory,
          priority: s.priority as SignalPriority,
          status: (s.status as SignalStatus) || "new",
          timestamp: (s.timestamp as string) || "Recently",
          relatedPage: s.relatedPage as string | undefined,
          relatedChannel: s.relatedChannel as string | undefined,
          source: (s.source as string) || "",
          issueIds: Array.isArray(s.issueIds) ? s.issueIds as string[] : undefined,
          revenueImpact: (s.revenueImpact === "high" || s.revenueImpact === "medium" || s.revenueImpact === "low")
            ? s.revenueImpact
            : undefined,
        }));
        setSignals(mapped);
        setLoadError(null);
      }
    } catch (err) {
      // Network error / abort. Don't wipe signals on a transient
      // failure; surface the error instead so the user can retry.
      console.warn("[signals] load failed", err);
      setLoadError("Couldn't reach the signals API. Check your connection and retry.");
    }
    setLoading(false);
  }, [brand.id]);

  useEffect(() => { loadSignals(); }, [loadSignals]);

  // Filter out dismissed signals ONCE so everything downstream (counts,
  // tab counts, tile list) agrees on which signals are "active". Using
  // the raw `signals` array here was the root cause of stale counts —
  // a dismiss shifted the `dismissed` Set but the Critical/Opportunities/
  // Wins/Revenue summary cards still counted dismissed rows.
  const visible = signals.filter((s) => !dismissed.has(s.id));

  // Summary card counts. Derived from `visible` so dismissals update
  // them live. "Critical" groups both "critical" AND "high" priority
  // issues — the signal generator tends to emit high-priority rows
  // more often than critical, so restricting to priority==="critical"
  // made the summary card read 0 on real data. Now: priority === high
  // OR critical within category === issue.
  const counts = {
    critical: visible.filter((s) => s.category === "issue" && (s.priority === "critical" || s.priority === "high")).length,
    opportunities: visible.filter((s) => s.category === "opportunity").length,
    wins: visible.filter((s) => s.category === "win").length,
    revenue: visible.filter((s) => s.category === "revenue").length,
  };

  const newCount = visible.filter((s) => s.status === "new").length;

  function markViewed(id: string) {
    setSignals((prev) => prev.map((s) => s.id === id && s.status === "new" ? { ...s, status: "viewed" as SignalStatus } : s));
  }

  function markRead(id: string) {
    setSignals((prev) => prev.map((s) => s.id === id ? { ...s, status: "viewed" as SignalStatus } : s));
  }

  async function markActioned(id: string) {
    const signal = signals.find((s) => s.id === id);
    if (!signal) return;

    // Prevent re-entry while creating or already actioned
    const current = taskFeedback[id];
    if (current === "Creating..." || current === "Task Created" || current === "Already exists") return;

    setTaskFeedback((prev) => ({ ...prev, [id]: "Creating..." }));

    try {
      const res = await fetch("/api/tasks", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "create",
          brandId: brand.id,
          title: `Fix: ${signal.title}`,
          description: signal.description,
          category: signal.category === "ai" ? "llmo" : signal.category === "execution" ? "technical" : "seo",
          priority: signal.priority,
          source: signal.source,
          sourceId: signal.issueIds?.[0] || signal.id,
        }),
      });

      if (res.ok) {
        const data = await res.json();
        setSignals((prev) => prev.map((s) => s.id === id ? { ...s, status: "actioned" as SignalStatus } : s));
        setTaskFeedback((prev) => ({ ...prev, [id]: data._duplicate ? "Already exists" : "Task Created" }));
      } else {
        // Revert — signal stays in its original status, feedback shows error
        setTaskFeedback((prev) => ({ ...prev, [id]: "Failed" }));
        setTimeout(() => setTaskFeedback((prev) => { const n = { ...prev }; delete n[id]; return n; }), 4000);
      }
    } catch {
      setTaskFeedback((prev) => ({ ...prev, [id]: "Failed" }));
      setTimeout(() => setTaskFeedback((prev) => { const n = { ...prev }; delete n[id]; return n; }), 4000);
    }
  }

  /** Dismiss a signal. Optimistically marks it dismissed in local
   *  state so the row disappears + counts update immediately, then
   *  persists to the server via POST /api/signals/dismiss. On server
   *  failure we REVERT the optimistic update — leaving the client
   *  insisting the signal is gone while the server still reports it
   *  would cause the signal to reappear on the next poll/refresh,
   *  which reads as a bug. Better to surface the failure and let
   *  the user retry.
   */
  async function dismissSignal(id: string) {
    // Optimistic: add to local dismissed set first so the UI
    // updates instantly (counts + list filter both derive from
    // `dismissed` via the `visible` memo).
    setDismissed((prev) => { const n = new Set(Array.from(prev)); n.add(id); return n; });
    try {
      const res = await fetch("/api/signals/dismiss", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ brandId: brand.id, signalId: id }),
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
    } catch (err) {
      console.warn("[signals] dismiss failed — reverting optimistic update", err);
      setDismissed((prev) => {
        const n = new Set(Array.from(prev));
        n.delete(id);
        return n;
      });
      setLoadError("Couldn't dismiss that signal. Check your connection and retry.");
    }
  }

  function togglePin(id: string) {
    setPinned((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  }

  // Filter list: `visible` already computed above. Apply the category
  // filter on top so the tile list honours whichever tab is active.
  const activeSignals = filter === "all" ? visible : visible.filter((s) => s.category === filter);

  // Pinned signals always on top
  const pinnedSignals = activeSignals.filter((s) => pinned.has(s.id));
  const unpinnedSignals = activeSignals.filter((s) => !pinned.has(s.id));

  // Top 3 highlights (highest priority new signals, not pinned separately)
  const topHighlights = unpinnedSignals.filter((s) => s.status === "new" && (s.priority === "critical" || s.priority === "high")).slice(0, 3);
  const topHighlightIds = new Set(topHighlights.map((s) => s.id));

  // Group remaining by time
  function parseTimeGroup(timestamp: string): "today" | "this_week" | "earlier" {
    const lower = timestamp.toLowerCase();
    if (lower.includes("just now") || lower.includes("minute") || lower.includes("hour") || lower === "now" || lower === "this period") return "today";
    if (lower.includes("yesterday") || lower.includes("this week") || lower.includes("1 day") || lower.includes("2 day")) return "this_week";
    return "earlier";
  }

  const remainingSignals = unpinnedSignals.filter((s) => !topHighlightIds.has(s.id));
  const groups: { label: string; key: string; signals: Signal[] }[] = [
    { label: "Today", key: "today", signals: remainingSignals.filter((s) => parseTimeGroup(s.timestamp) === "today") },
    { label: "This Week", key: "this_week", signals: remainingSignals.filter((s) => parseTimeGroup(s.timestamp) === "this_week") },
    { label: "Earlier", key: "earlier", signals: remainingSignals.filter((s) => parseTimeGroup(s.timestamp) === "earlier") },
  ];

  return (
    <div className="space-y-6">

      {/* ── Header ── */}
      <div className="flex items-start justify-between gap-4">
        <div className="flex items-center gap-3">
          <div className="w-10 h-10 rounded-xl bg-brand-50 flex items-center justify-center shrink-0 relative">
            <Bell className="w-5 h-5 text-brand-600" />
            {newCount > 0 && (
              <span className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-red-500 text-white text-[10px] font-bold flex items-center justify-center">{newCount}</span>
            )}
          </div>
          <div>
            <h1 className="text-2xl font-bold text-slate-900">Signals Center</h1>
            <p className="text-slate-500 text-sm mt-0.5">Important insights, risks, wins, and opportunities across your platform.</p>
          </div>
        </div>
      </div>

      {/* Load-error banner — surfaces a failed /api/signals refresh so
          the user can tell the difference between an empty signal feed
          and a stale one (previously identical on-screen). Amber rather
          than red because any last-known signals are still rendered
          below; only the refresh itself failed. */}
      {loadError && (
        <div className="rounded-xl px-4 py-3 text-sm bg-amber-50 text-amber-800 border border-amber-200 flex items-start justify-between gap-3">
          <span>{loadError}</span>
          <button
            onClick={() => loadSignals()}
            className="text-xs font-semibold underline shrink-0"
          >
            Retry
          </button>
        </div>
      )}

      {/* ── 1. Overview Cards ── */}
      <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
        {([
          // Card counts `counts.critical` which — despite the name — sums
          // both "critical" AND "high" priority issues (see counts derivation
          // above). Relabelled from "Critical Issues" → "Critical & High"
          // so the label matches the number users actually see. Subtitle
          // explicitly says both severities too, in case the card title
          // gets truncated on narrow layouts.
          { key: "issue" as FilterKey, label: "Critical & High", count: counts.critical, icon: AlertCircle, color: "red", iconColor: "text-red-500", textColor: "text-red-600", border: "border-red-200", sub: "Critical + high-priority issues" },
          { key: "opportunity" as FilterKey, label: "Opportunities", count: counts.opportunities, icon: Zap, color: "blue", iconColor: "text-blue-500", textColor: "text-blue-600", border: "border-blue-200", sub: "Growth potential detected" },
          { key: "win" as FilterKey, label: "Wins", count: counts.wins, icon: TrendingUp, color: "green", iconColor: "text-green-500", textColor: "text-green-600", border: "border-green-200", sub: "Positive results this period" },
          { key: "revenue" as FilterKey, label: "Revenue Alerts", count: counts.revenue, icon: DollarSign, color: "emerald", iconColor: "text-emerald-500", textColor: "text-emerald-600", border: "border-emerald-200", sub: "Revenue-related signals" },
        ] as const).map((card) => {
          const Icon = card.icon;
          return (
            <button
              key={card.key}
              onClick={() => setFilter(filter === card.key ? "all" : card.key)}
              className={`bg-white rounded-xl border ${filter === card.key ? "border-slate-900 ring-1 ring-slate-900" : card.border} px-5 py-4 text-left hover:shadow-sm transition-all`}
            >
              <div className="flex items-center gap-2 mb-2">
                <Icon className={`w-4 h-4 ${card.iconColor}`} />
                <p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{card.label}</p>
              </div>
              <p className={`text-3xl font-black ${card.textColor}`}>{card.count}</p>
              <p className="text-[11px] text-slate-500 mt-1">{card.sub}</p>
            </button>
          );
        })}
      </div>

      {/* ── KPI Tile Inline Summary ── */}
      {filter !== "all" && (() => {
        const matched = filter === "issue"
          ? visible.filter((s) => s.category === "issue" && (s.priority === "critical" || s.priority === "high"))
          : visible.filter((s) => s.category === filter);
        if (matched.length === 0) return null;

        const pages = new Set<string>();
        for (const s of matched) if (s.relatedPage) pages.add(s.relatedPage);

        // Top issues by frequency of source/title prefix
        const titleCounts = new Map<string, number>();
        for (const s of matched) {
          const key = s.title.split(":")[0].trim().slice(0, 60);
          titleCounts.set(key, (titleCounts.get(key) ?? 0) + 1);
        }
        const topIssues = [...titleCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);

        const filterLabel = filter === "issue" ? "critical & high issues"
          : filter === "opportunity" ? "growth opportunities"
          : filter === "win" ? "wins"
          : filter === "revenue" ? "revenue alerts"
          : filter === "ai" ? "AI signals"
          : "execution items";

        return (
          <div className="bg-slate-50 rounded-xl border border-slate-200 px-5 py-4 space-y-2">
            <div className="flex items-start justify-between gap-3">
              <div className="flex-1">
                <p className="text-sm font-bold text-slate-900">
                  {matched.length} {filterLabel}
                  {pages.size > 0 && (
                    <span className="font-normal text-slate-600"> affecting {pages.size} page{pages.size !== 1 ? "s" : ""}</span>
                  )}
                </p>
                {topIssues.length > 0 && (
                  <p className="text-xs text-slate-600 mt-1">
                    <span className="font-semibold">Top:</span>{" "}
                    {topIssues.map(([title, cnt], i) => (
                      <span key={title}>
                        {i > 0 && " · "}
                        {title} <span className="text-slate-400">({cnt})</span>
                      </span>
                    ))}
                  </p>
                )}
              </div>
              <button
                onClick={() => setFilter("all")}
                className="text-[10px] font-semibold text-slate-500 hover:text-slate-700 shrink-0"
              >
                Clear filter
              </button>
            </div>
          </div>
        );
      })()}

      {/* ── 2. Filter Bar ── */}
      <div className="flex items-center gap-1.5 flex-wrap">
        {FILTERS.map((f) => {
          const Icon = f.icon;
          const count = f.key === "all" ? visible.length : visible.filter((s) => s.category === f.key).length;
          return (
            <button
              key={f.key}
              onClick={() => setFilter(f.key)}
              className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold transition-colors ${
                filter === f.key
                  ? "bg-brand-600 text-white shadow-sm"
                  : "bg-white border border-slate-200 text-slate-600 hover:bg-slate-50"
              }`}
            >
              <Icon className="w-3 h-3" />
              {f.label}
              <span className={`ml-0.5 text-[10px] ${filter === f.key ? "text-brand-200" : "text-slate-400"}`}>({count})</span>
            </button>
          );
        })}
      </div>

      {/* ── 3. Top Highlights ── */}
      {topHighlights.length > 0 && (
        <div className="space-y-2">
          <div className="flex items-center gap-2">
            <Sparkles className="w-4 h-4 text-brand-600" />
            <h2 className="text-sm font-bold text-slate-800">Priority Signals</h2>
            <span className="text-[10px] font-semibold text-brand-600 bg-brand-50 border border-brand-200 rounded-full px-2 py-0.5">{topHighlights.length} urgent</span>
          </div>
          <div className="grid grid-cols-1 lg:grid-cols-3 gap-3">
            {topHighlights.map((signal) => {
              const cat = CATEGORY_CFG[signal.category];
              const CatIcon = cat.icon;
              return (
                <div key={signal.id} className={`bg-white rounded-xl border-2 ${cat.border} px-4 py-3.5 hover:shadow-md transition-all`}>
                  <div className="flex items-center gap-2 mb-2">
                    <div className={`w-7 h-7 rounded-lg ${cat.bg} flex items-center justify-center shrink-0`}>
                      <CatIcon className={`w-3.5 h-3.5 ${cat.color}`} />
                    </div>
                    <span className={`text-[9px] font-bold uppercase tracking-wide ${cat.color}`}>{cat.label}</span>
                    <span className="text-[9px] font-bold text-red-600 bg-red-50 border border-red-200 rounded px-1.5 py-0.5 ml-auto">{signal.priority}</span>
                    {signal.revenueImpact && <RevenueImpactBadge impact={signal.revenueImpact} />}
                  </div>
                  <h3 className="text-sm font-bold text-slate-900 leading-snug">{signal.title}</h3>
                  <p className="text-[11px] text-slate-500 mt-1 line-clamp-2">{stripMarkdown(signal.description)}</p>
                  <div className="flex items-center gap-2 mt-2.5">
                    <button onClick={() => markActioned(signal.id)} className="flex items-center gap-1 px-2 py-1 rounded border border-brand-200 bg-brand-50 text-[10px] font-semibold text-brand-700 hover:bg-brand-100 transition-colors">
                      <ClipboardPlus className="w-2.5 h-2.5" /> Create Task
                    </button>
                    <button onClick={() => togglePin(signal.id)} className={`p-1 rounded transition-colors ${pinned.has(signal.id) ? "bg-amber-50 text-amber-600" : "text-slate-400 hover:text-slate-600"}`}>
                      <Pin className="w-3 h-3" />
                    </button>
                    <span className="text-[10px] text-slate-400 ml-auto">{signal.timestamp}</span>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {/* ── 4. Pinned Signals ── */}
      {pinnedSignals.length > 0 && (
        <div className="space-y-2">
          <div className="flex items-center gap-2">
            <Pin className="w-4 h-4 text-amber-500" />
            <h2 className="text-sm font-bold text-slate-800">Pinned</h2>
          </div>
          <div className="space-y-2">
            {pinnedSignals.map((signal) => renderSignalCard(signal, true))}
          </div>
        </div>
      )}

      {/* ── 5. Grouped Feed ── */}
      {groups.map((group) => {
        if (group.signals.length === 0) return null;
        return (
          <div key={group.key} className="space-y-2">
            <div className="flex items-center gap-2">
              <Clock className="w-3.5 h-3.5 text-slate-400" />
              <h2 className="text-xs font-bold text-slate-400 uppercase tracking-widest">{group.label}</h2>
              <span className="text-[10px] text-slate-300">{group.signals.length}</span>
            </div>
            <div className="space-y-2">
              {group.signals.map((signal) => renderSignalCard(signal, false))}
            </div>
          </div>
        );
      })}

      {activeSignals.length === 0 && (
        <div className="bg-white rounded-xl border border-slate-200 px-6 py-12 text-center">
          <Bell className="w-8 h-8 text-slate-300 mx-auto mb-3" />
          <p className="text-sm font-semibold text-slate-500">No signals for this filter</p>
          <p className="text-xs text-slate-400 mt-1">Try selecting a different category above.</p>
        </div>
      )}
    </div>
  );

  // ─── Signal card renderer ──────────────────────────────────────────────────

  function renderSignalCard(signal: Signal, isPinnedCard: boolean) {
    const cat = CATEGORY_CFG[signal.category];
    const pri = PRIORITY_CFG[signal.priority];
    const st = STATUS_CFG[signal.status];
    const CatIcon = cat.icon;
    const StIcon = st.icon;

    return (
      <div
        key={signal.id}
        onClick={() => markViewed(signal.id)}
        className={`bg-white rounded-xl border overflow-hidden transition-all cursor-pointer group ${
          isPinnedCard ? "border-amber-200 bg-amber-50/20" :
          signal.status === "new" ? `${cat.border} shadow-sm ring-1 ring-offset-1 ${cat.border}` : "border-slate-200"
        }`}
      >
        {/* Severity bar — colored left edge */}
        <div className="flex">
          <div className={`w-1 shrink-0 ${pri.dot}`} />
          <div className="flex-1 min-w-0">
        <div className="flex items-start gap-4 px-5 py-4">
          <div className={`w-10 h-10 rounded-xl ${cat.bg} ${cat.border} border flex items-center justify-center shrink-0 mt-0.5`}>
            <CatIcon className={`w-5 h-5 ${cat.color}`} />
          </div>

          <div className="flex-1 min-w-0">
            <div className="flex items-center gap-2 flex-wrap mb-1">
              {signal.status === "new" && <span className={`w-2 h-2 rounded-full ${cat.dot} animate-pulse`} />}
              {isPinnedCard && <Pin className="w-3 h-3 text-amber-500" />}
              <span className={`text-[10px] font-bold uppercase tracking-wide px-2 py-0.5 rounded border ${cat.bg} ${cat.border} ${cat.color}`}>{cat.label}</span>
              <span className={`text-[10px] font-bold uppercase tracking-wide px-2 py-0.5 rounded border ${pri.bg} ${pri.color}`}>{pri.label}</span>
              {signal.revenueImpact && <RevenueImpactBadge impact={signal.revenueImpact} />}
              <span className={`flex items-center gap-1 text-[10px] font-semibold ${st.color}`}>
                <StIcon className="w-3 h-3" />
                {st.label}
              </span>
              <span className="text-[10px] text-slate-400 ml-auto shrink-0 flex items-center gap-1">
                <Clock className="w-3 h-3" />
                {signal.timestamp}
              </span>
            </div>

            <h3 className={`text-sm font-bold ${signal.status === "new" ? "text-slate-900" : "text-slate-700"}`}>{signal.title}</h3>
            <p className="text-xs text-slate-500 mt-1 leading-relaxed line-clamp-2">{stripMarkdown(signal.description)}</p>

            {/* Recommended Action — rendered when the signal carries an
                AI-generated next step (signal_ai_* sources). Distinct
                indigo tint so it reads as advice, not as more problem
                description. */}
            {signal.suggestedAction && (
              <div className="mt-2 px-3 py-2 rounded-lg bg-indigo-50 border border-indigo-100">
                <p className="text-[10px] font-bold uppercase tracking-widest text-indigo-700 mb-0.5">Recommended Action</p>
                <MarkdownContent className="text-xs text-indigo-900">{signal.suggestedAction}</MarkdownContent>
              </div>
            )}

            <div className="flex items-center gap-2 mt-2.5 flex-wrap">
              {signal.relatedPage && (
                <span className="flex items-center gap-1 text-[10px] font-mono text-brand-600 bg-brand-50 border border-brand-200 rounded px-2 py-0.5">
                  <ExternalLink className="w-2.5 h-2.5" />
                  {signal.relatedPage}
                </span>
              )}
              {signal.relatedChannel && (
                <span className="flex items-center gap-1 text-[10px] font-semibold text-slate-600 bg-slate-50 border border-slate-200 rounded px-2 py-0.5">
                  {signal.relatedChannel}
                </span>
              )}
              {signal.competitor && (
                <span className="flex items-center gap-1 text-[10px] font-semibold text-slate-700 bg-slate-100 border border-slate-200 rounded px-2 py-0.5">
                  vs {signal.competitor}
                </span>
              )}
              {/* Source badge — distinguishes AI-generated insights from
                  deterministic detectors so users can trust the rec flow. */}
              {signal.source === "site-tag-ai" && (
                <span className="text-[10px] font-semibold text-violet-700 bg-violet-50 border border-violet-200 rounded px-2 py-0.5">
                  AI Scan
                </span>
              )}
              {signal.source === "competitor-regional-ai" && (
                <span className="text-[10px] font-semibold text-fuchsia-700 bg-fuchsia-50 border border-fuchsia-200 rounded px-2 py-0.5">
                  Competitor Intel
                </span>
              )}
              {signal.source === "site-tag" && (
                <span className="text-[10px] font-semibold text-slate-600 bg-slate-50 border border-slate-200 rounded px-2 py-0.5">
                  Site Tag
                </span>
              )}

              <div className="flex items-center gap-1.5 ml-auto">
                {/* Pin */}
                <button
                  onClick={(e) => { e.stopPropagation(); togglePin(signal.id); }}
                  className={`p-1.5 rounded-lg transition-colors ${pinned.has(signal.id) ? "bg-amber-50 text-amber-600 border border-amber-200" : "text-slate-300 hover:text-amber-500 opacity-0 group-hover:opacity-100"}`}
                  title={pinned.has(signal.id) ? "Unpin" : "Pin signal"}
                >
                  <Pin className="w-3 h-3" />
                </button>

                {/* Mark read */}
                {signal.status === "new" && (
                  <button
                    onClick={(e) => { e.stopPropagation(); markRead(signal.id); }}
                    className="p-1.5 rounded-lg text-slate-300 hover:text-slate-600 opacity-0 group-hover:opacity-100 transition-colors"
                    title="Mark as read"
                  >
                    <Eye className="w-3 h-3" />
                  </button>
                )}

                {/* Dismiss */}
                <button
                  onClick={(e) => { e.stopPropagation(); dismissSignal(signal.id); }}
                  className="p-1.5 rounded-lg text-slate-300 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-colors"
                  title="Dismiss"
                >
                  <X className="w-3 h-3" />
                </button>

                {/* Details toggle — expands the tile inline so the
                    user can read the full description + suggested
                    action without leaving the page. The previous Link
                    jumped to a source-specific page which was often
                    not helpful ("technical_audit" → /technical-audit
                    with no way to jump back). The expanded body still
                    exposes an "Open page" deep-link as a secondary
                    affordance for the source types that actually
                    have a meaningful destination. */}
                <button
                  onClick={(e) => {
                    e.stopPropagation();
                    setExpandedId((cur) => {
                      const next = cur === signal.id ? null : signal.id;
                      // Trigger AI recs fetch when opening, fire-and-forget
                      if (next === signal.id) loadRecsForSignal(signal);
                      return next;
                    });
                  }}
                  className={`flex items-center gap-1 px-2.5 py-1 rounded-lg border text-[10px] font-semibold transition-colors ${expandedId === signal.id ? "border-slate-300 bg-slate-100 text-slate-800" : "border-slate-200 text-slate-600 hover:bg-slate-50"}`}
                  aria-expanded={expandedId === signal.id}
                >
                  <ChevronRight className={`w-3 h-3 transition-transform ${expandedId === signal.id ? "rotate-90" : ""}`} />
                  {expandedId === signal.id ? "Hide" : "Details"}
                </button>

                {signal.status !== "actioned" && !taskFeedback[signal.id] ? (
                  <button
                    onClick={(e) => { e.stopPropagation(); markActioned(signal.id); }}
                    className="flex items-center gap-1 px-2.5 py-1 rounded-lg border border-brand-200 bg-brand-50 text-[10px] font-semibold text-brand-700 hover:bg-brand-100 transition-colors"
                  >
                    <ClipboardPlus className="w-3 h-3" />
                    Task
                  </button>
                ) : taskFeedback[signal.id] ? (
                  <span className="flex items-center gap-1 px-2.5 py-1 rounded-lg bg-green-50 border border-green-200 text-[10px] font-semibold text-green-700">
                    <CheckCircle2 className="w-3 h-3" />
                    Created
                  </span>
                ) : null}
              </div>
            </div>
          </div>
        </div>

        {/* Expanded details — rich detail panel with analysis,
            recommendations, affected pages, and one-click actions. */}
        {expandedId === signal.id && (
          <div className="border-t border-slate-100 bg-slate-50/60 divide-y divide-slate-100">
            {/* Section A — Signal Overview */}
            <div className="px-5 py-4 space-y-2">
              <div className="flex items-center gap-2 flex-wrap">
                <span className={`text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded ${pri.color}`}>{pri.label}</span>
                <span className={`text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded ${cat.color} border ${cat.border}`}>{cat.label}</span>
                <span className={`text-[10px] font-semibold px-2 py-0.5 rounded ${st.color} border ${st.border}`}>{st.label}</span>
                {signal.revenueImpact && <RevenueImpactBadge impact={signal.revenueImpact} />}
              </div>
              <p className="text-sm font-bold text-slate-900">{signal.title}</p>
              <div className="flex items-center gap-3 text-[10px] text-slate-500">
                <span>Detected: {signal.timestamp}</span>
                <span>Source: {signal.source.replace(/_/g, " ")}</span>
                {signal.relatedChannel && <span>Channel: {signal.relatedChannel}</span>}
                {signal.competitor && <span>Competitor: {signal.competitor}</span>}
              </div>
            </div>

            {/* Section B — Full Description & Analysis */}
            <div className="px-5 py-4 space-y-3">
              <p className="text-[10px] font-bold uppercase tracking-widest text-slate-400">What we found</p>
              <MarkdownContent className="text-xs text-slate-700">{signal.description}</MarkdownContent>

              {signal.relatedPage && (
                <div>
                  <p className="text-[10px] font-bold uppercase tracking-widest text-slate-400 mb-1">Affected page</p>
                  <a
                    href={signal.relatedPage.startsWith("http") ? signal.relatedPage : `https://${brand.domain}${signal.relatedPage}`}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="text-xs font-mono text-brand-600 hover:underline inline-flex items-center gap-1 bg-white border border-slate-200 rounded-lg px-3 py-1.5"
                  >
                    {signal.relatedPage}
                    <ExternalLink className="w-3 h-3" />
                  </a>
                </div>
              )}
            </div>

            {/* Impact Estimate (AI-generated) */}
            {(() => {
              const recState = recsBySignal[signal.id];
              if (!recState?.impactEstimate) return null;
              return (
                <div className="px-5 py-4">
                  <p className="text-[10px] font-bold uppercase tracking-widest text-slate-400 mb-1.5">Impact estimate</p>
                  <div className="bg-blue-50 border border-blue-100 rounded-lg p-3">
                    <MarkdownContent className="text-xs text-blue-900">{recState.impactEstimate}</MarkdownContent>
                  </div>
                </div>
              );
            })()}

            {/* Section C — AI Recommendations (loaded on demand) */}
            <div className="px-5 py-4 space-y-2">
              <div className="flex items-center gap-2">
                <p className="text-[10px] font-bold uppercase tracking-widest text-slate-400">AI Recommendations</p>
                <Sparkles className="w-3 h-3 text-amber-500" />
              </div>
              {(() => {
                const recState = recsBySignal[signal.id];
                if (!recState || recState.loading) {
                  return (
                    <div className="space-y-2">
                      {[0, 1, 2].map((i) => (
                        <div key={i} className="bg-white rounded-lg border border-slate-200 p-3 animate-pulse">
                          <div className="h-3 w-1/3 bg-slate-200 rounded mb-2" />
                          <div className="h-2.5 w-full bg-slate-100 rounded mb-1" />
                          <div className="h-2.5 w-2/3 bg-slate-100 rounded" />
                        </div>
                      ))}
                      <p className="text-[10px] text-slate-400 italic">Generating recommendations…</p>
                    </div>
                  );
                }
                if (recState.error) {
                  return (
                    <div className="bg-amber-50 border border-amber-200 rounded-lg p-3">
                      <p className="text-xs text-amber-800">Couldn&apos;t generate AI recommendations: {recState.error}</p>
                      {signal.suggestedAction && (
                        <p className="text-xs text-slate-600 mt-2">Fallback: {signal.suggestedAction}</p>
                      )}
                    </div>
                  );
                }
                if (recState.recommendations.length === 0) {
                  return (
                    <div className="bg-slate-50 border border-slate-200 rounded-lg p-3">
                      <p className="text-xs text-slate-500">No specific recommendations generated. Review the signal manually.</p>
                    </div>
                  );
                }
                return (
                  <div className="space-y-2">
                    {recState.recommendations.map((rec, idx) => {
                      const priColor = rec.priority === "critical" ? "bg-red-50 border-red-200 text-red-700"
                        : rec.priority === "high" ? "bg-orange-50 border-orange-200 text-orange-700"
                        : rec.priority === "medium" ? "bg-amber-50 border-amber-200 text-amber-700"
                        : "bg-slate-50 border-slate-200 text-slate-600";
                      return (
                        <div key={idx} className="bg-white rounded-lg border border-slate-200 p-3 space-y-2">
                          <div className="flex items-start justify-between gap-2">
                            <div className="flex-1 min-w-0">
                              <div className="flex items-center gap-1.5 mb-1">
                                <span className={`text-[9px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded border ${priColor}`}>
                                  {rec.priority}
                                </span>
                                {rec.fixType && (
                                  <span className="text-[9px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded bg-slate-100 text-slate-500 border border-slate-200">
                                    {rec.fixType}
                                  </span>
                                )}
                              </div>
                              <p className="text-xs font-bold text-slate-800">{rec.title}</p>
                              <MarkdownContent className="text-xs text-slate-600 mt-0.5">{rec.description}</MarkdownContent>
                            </div>
                          </div>
                          {rec.suggestedFix && (
                            <div className="bg-slate-50 border border-slate-200 rounded p-2 space-y-1.5">
                              <div className="flex items-center justify-between">
                                <p className="text-[9px] font-bold uppercase tracking-wider text-slate-400">Suggested fix</p>
                                <button
                                  onClick={(e) => {
                                    e.stopPropagation();
                                    navigator.clipboard.writeText(rec.suggestedFix ?? "").catch(() => null);
                                  }}
                                  className="text-[9px] font-semibold text-brand-600 hover:text-brand-700"
                                >
                                  Copy
                                </button>
                              </div>
                              <pre className="text-[10px] text-slate-700 font-mono whitespace-pre-wrap break-words">{rec.suggestedFix}</pre>
                            </div>
                          )}
                        </div>
                      );
                    })}
                  </div>
                );
              })()}
            </div>

            {/* Section F — Action Buttons */}
            <div className="px-5 py-3 flex flex-wrap items-center gap-2">
              {signal.relatedPage && (
                <button
                  onClick={(e) => {
                    e.stopPropagation();
                    const kw = signal.relatedPage?.replace(/^\/|\/$/g, "").split("/").pop()?.replace(/-/g, " ") ?? "";
                    window.open(`/content-brief?keyword=${encodeURIComponent(kw)}`, "_blank");
                  }}
                  className="inline-flex items-center gap-1 px-3 py-1.5 text-[10px] font-semibold bg-brand-600 text-white rounded-lg hover:bg-brand-700"
                >
                  Create SEO Brief
                </button>
              )}
              {signal.relatedPage && (
                <button
                  onClick={(e) => {
                    e.stopPropagation();
                    const kw = signal.relatedPage?.replace(/^\/|\/$/g, "").split("/").pop()?.replace(/-/g, " ") ?? "";
                    window.open(`/llmo-content-brief?keyword=${encodeURIComponent(kw)}`, "_blank");
                  }}
                  className="inline-flex items-center gap-1 px-3 py-1.5 text-[10px] font-semibold bg-violet-600 text-white rounded-lg hover:bg-violet-700"
                >
                  <Bot className="w-3 h-3" /> Create LLMO Brief
                </button>
              )}
              {(() => {
                const deepLink =
                  signal.source === "technical_audit" && signal.issueIds?.length === 1
                    ? `/issue-detail?issueId=${signal.issueIds[0]}&source=technical_audit&brandId=${brand.id}&domain=${encodeURIComponent(brand.domain)}`
                    : signal.source === "technical_audit" ? "/technical-audit"
                    : signal.source === "performance_audit" ? "/site-performance-audit"
                    : signal.source === "dead_pages" ? "/broken-links"
                    : signal.source === "execution_hub" ? "/execution-hub"
                    : signal.source === "ai_visibility" ? "/ai-visibility"
                    : signal.source === "conversions" ? "/site-tag-analytics"
                    : signal.source === "rank_tracker" ? "/rank-tracker"
                    : signal.source === "site_tag" ? "/site-tag-analytics"
                    : signal.source === "ai-strategy-engine" ? "/ai-recommendations"
                    : null;
                if (!deepLink) return null;
                return (
                  <button
                    onClick={(e) => { e.stopPropagation(); window.open(deepLink, "_blank"); }}
                    className="inline-flex items-center gap-1 px-3 py-1.5 text-[10px] font-semibold border border-slate-200 text-slate-600 bg-white rounded-lg hover:bg-slate-50"
                  >
                    <ExternalLink className="w-3 h-3" /> View in source
                  </button>
                );
              })()}
              <button
                onClick={(e) => { e.stopPropagation(); dismissSignal(signal.id); }}
                className="inline-flex items-center gap-1 px-3 py-1.5 text-[10px] font-semibold border border-slate-200 text-slate-500 bg-white rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 ml-auto"
              >
                <X className="w-3 h-3" /> Dismiss
              </button>
            </div>
          </div>
        )}
          </div>{/* end flex-1 */}
        </div>{/* end flex (severity bar wrapper) */}
      </div>
    );
  }
}

/** Revenue-impact pill. Only rendered when the parent signal has a
 *  `revenueImpact` value — the derivation skips low-signal rows so
 *  the badge stays meaningful instead of appearing on every tile.
 *  Three bands mirror the rest of the app's severity palette:
 *  rose for high (distinct from the red "Critical" priority pill so
 *  the two don't camouflage), amber for medium, slate for low. */
function RevenueImpactBadge({ impact }: { impact: "high" | "medium" | "low" }) {
  const cfg = impact === "high"   ? { color: "text-rose-700  bg-rose-50  border-rose-200",  label: "High Rev" }
            : impact === "medium" ? { color: "text-amber-700 bg-amber-50 border-amber-200", label: "Med Rev"  }
            :                       { color: "text-slate-600 bg-slate-50 border-slate-200", label: "Low Rev"  };
  return (
    <span
      className={`text-[9px] font-bold uppercase tracking-wide px-1.5 py-0.5 rounded border ${cfg.color}`}
      title={`${impact === "high" ? "High" : impact === "medium" ? "Medium" : "Low"} revenue impact`}
    >
      {cfg.label}
    </span>
  );
}
