'use client';

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import type { ColumnDef } from '@tanstack/react-table';
import { api } from '@/lib/api';
import { fetchEmailTemplates, fillTemplate, type EmailTemplate } from '@/lib/notificationTemplates';
import { useAuth } from '@/components/auth-context';
import JobPostCard from '@/components/JobPostCard';
import { DataTable } from '@/components/data-table/DataTable';
import { toast } from 'react-toastify';
import { formatDateTime } from '@/lib/dates';
import { cleanHtmlText } from '@/lib/format';
import { showSuccess, showError, showConfirm } from '@/lib/confirm';
import { isSourceEmpty, sourceLabel, SOURCE_EMPTY } from '@/lib/sources';

interface Posting {
  id: number;
  channel: string;
  channel_label: string;
  status: 'PENDING' | 'POSTED' | 'FAILED';
  external_post_id: string;
  external_url: string;
  error_message: string;
  posted_by_email: string | null;
  posted_at: string | null;
  updated_at: string;
}

interface JobDetail {
  id: number;
  jd_code?: string;
  title: string;
  department?: string;
  location: string;
  experience_band?: string;
  ctc_band?: string;
  notice_period?: string;
  shift?: string;
  must_have_skills?: string;
  good_to_have_skills?: string;
  qualifications?: string;
  work_details: string;
  status: string;
  priority?: string;
  created_by_email: string;
  created_by_name?: string | null;
  client_name?: string | null;
  created_at: string;
  working_days?: string;
  num_positions?: number | null;
  certification?: string;
  questions?: (string | { question: string; answer: string })[];
  /** Single canonical status: draft | pending_approval | published | closed. */
  jd_status?: string;
}

interface PublishResult {
  channel: string;
  status: string;
  external_url: string;
  error_message: string;
}

/** One row of GET /ai-calls/history/ — an AI call log entry. Score, recommendation
 *  and summary are withheld server-side unless `is_completed`. */
interface CallHistoryRow {
  call_id: number;
  candidate_id: number;
  candidate_name: string;
  mobile_number: string;
  job_id: number | null;
  job_title: string;
  recruiter: string;
  /** The Hunar agent that ran the call — raw id, plus its resolved name/code.
   *  name/code are blank when Hunar could not be reached. */
  ai_agent: string;
  ai_agent_name?: string;
  ai_agent_code?: string;
  status: string;
  duration: number | null;
  score: number | null;
  recommendation: string;
  started_at: string;
  ended_at: string;
  updated_at: string;
  summary: string;
  error_message: string;
  has_transcript: boolean;
  is_completed: boolean;
  recording_url?: string;
  /** Call finished but Hunar hasn't returned its summary yet. */
  summary_pending?: boolean;
}

/** GET /ai-calls/<id>/transcript/ — the existing Hunar call summary payload. */
interface CallSummaryData {
  candidate?: string;
  job_title?: string;
  status?: string;
  duration?: number | null;
  started_at?: string;
  summary?: string;
  recording_url?: string;
  transcript?: string;
  score?: number | null;
  recommendation?: string;
  utterances?: { sequence: number; speaker: string; message: string }[];
  evaluation?: {
    overall_score: number | null;
    classification: string;
    recommendation: string;
    strengths: string[];
    weaknesses: string[];
    summary: string;
    /** Per-question scores / evidence quotes Hunar returned — "Key Responses". */
    rubric?: Record<string, unknown> | null;
  } | null;
}

/** Real Hunar call statuses only — mirrors AICall.Status, no simulated values. */
const CALL_STATUS_BADGES: Record<string, { cls: string; label: string; icon: string; pulse?: boolean }> = {
  QUEUED: { cls: 'bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-300', label: 'Queued', icon: 'fa-clock', pulse: true },
  DIALING: { cls: 'bg-cyan-50 text-cyan-700 dark:bg-cyan-950/40 dark:text-cyan-300', label: 'Dialing', icon: 'fa-phone-volume', pulse: true },
  RINGING: { cls: 'bg-cyan-100 text-cyan-800 dark:bg-cyan-900/50 dark:text-cyan-200', label: 'Ringing', icon: 'fa-bell', pulse: true },
  CONNECTED: { cls: 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950/40 dark:text-indigo-300', label: 'Connected', icon: 'fa-microphone-lines', pulse: true },
  IN_PROGRESS: { cls: 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950/40 dark:text-indigo-300', label: 'In Progress', icon: 'fa-microphone-lines', pulse: true },
  COMPLETED: { cls: 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300', label: 'Completed', icon: 'fa-circle-check' },
  NO_ANSWER: { cls: 'bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300', label: 'No Answer', icon: 'fa-phone-slash' },
  BUSY: { cls: 'bg-orange-50 text-orange-700 dark:bg-orange-950/40 dark:text-orange-300', label: 'Busy', icon: 'fa-phone-slash' },
  REJECTED: { cls: 'bg-rose-50 text-rose-700 dark:bg-rose-950/40 dark:text-rose-300', label: 'Rejected', icon: 'fa-circle-xmark' },
  CANCELLED: { cls: 'bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-300', label: 'Cancelled', icon: 'fa-ban' },
  FAILED: { cls: 'bg-rose-50 text-rose-700 dark:bg-rose-950/40 dark:text-rose-300', label: 'Failed', icon: 'fa-triangle-exclamation' },
};

/** One previewed application URL from /jobs/<id>/publish-preview/. The `url`
 *  carries only a short opaque tracking token — never a readable `?source=`. */
interface PreviewLink {
  channel: string;
  channel_label: string;
  url: string;
  posting_status: string | null;
  is_posted: boolean;
}

interface JobDetailsContentProps {
  jobId: number | string;
  isModal?: boolean;
  onClose?: () => void;
  onJobLoaded?: (job: JobDetail) => void;
}

// Only LinkedIn is integrated and functional, so it is the sole publishing
// option shown. Other boards (Naukri, Career Portal, Indeed, Monster) are
// intentionally omitted from the UI until they are actually integrated.
const CHANNELS = [
  { key: 'LINKEDIN', label: 'LinkedIn', icon: 'fa-brands fa-linkedin', color: 'text-[#0a66c2]' },
  // { key: 'WHATSAPP', label: 'WhatsApp', icon: 'fa-brands fa-whatsapp', color: 'text-emerald-500' },
  // { key: 'SMS', label: 'SMS', icon: 'fa-solid fa-comment-sms', color: 'text-orange-500' },
  // { key: 'TELEGRAM', label: 'Telegram', icon: 'fa-brands fa-telegram', color: 'text-sky-500' },
  // { key: 'CAREER_PORTAL', label: 'Career Page / Direct Link', icon: 'fa-solid fa-globe', color: 'text-indigo-500' },
];

const STATUS_CFG: Record<Posting['status'], { label: string; dot: string; badge: string }> = {
  POSTED: { label: 'Posted', dot: 'bg-emerald-500', badge: 'bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-300 border-emerald-200 dark:border-emerald-900/40' },
  PENDING: { label: 'Pending', dot: 'bg-amber-400', badge: 'bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-300 border-amber-200 dark:border-amber-900/40' },
  FAILED: { label: 'Failed', dot: 'bg-rose-500', badge: 'bg-rose-50 dark:bg-rose-950/40 text-rose-700 dark:text-rose-300 border-rose-200 dark:border-rose-900/40' },
};

function friendlyTime(iso: string | null): string {
  if (!iso) return '--';
  const d = new Date(iso);
  if (isNaN(d.getTime())) return '--';
  const now = new Date();
  const time = d.toLocaleTimeString('en-IN', { hour: 'numeric', minute: '2-digit' });
  if (d.toDateString() === now.toDateString()) return `Today ${time}`;
  const yesterday = new Date(now); yesterday.setDate(now.getDate() - 1);
  if (d.toDateString() === yesterday.toDateString()) return `Yesterday ${time}`;
  return d.toLocaleDateString('en-IN', { day: 'numeric', month: 'short' }) + ` ${time}`;
}

// Single canonical status → one badge. Accepts the backend `jd_status`
// (draft|pending_approval|published|closed) and falls back to mapping the
// legacy `status` (Draft/Published/Closed) for older API responses.
const getStatusBadge = (canonical: string) => {
  const norm = (canonical || '').toLowerCase();
  if (norm === 'published') {
    return {
      label: 'Published',
      style: 'bg-emerald-50 dark:bg-emerald-950/50 text-emerald-700 dark:text-emerald-400 border-emerald-200 dark:border-emerald-800'
    };
  } else if (norm === 'pending_approval') {
    return {
      label: 'Pending Approval',
      style: 'bg-yellow-50 dark:bg-yellow-950/50 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800'
    };
  } else if (norm === 'closed') {
    return {
      label: 'Closed',
      style: 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 border-slate-200 dark:border-slate-700'
    };
  } else {
    return {
      label: 'Draft',
      style: 'bg-amber-50 dark:bg-amber-950/50 text-amber-700 dark:text-amber-400 border-amber-200 dark:border-amber-800'
    };
  }
};

/** Resolve the one canonical status value for a job. */
const canonicalStatus = (job: { jd_status?: string; status?: string }): string => {
  if (job.jd_status) return job.jd_status;
  const s = (job.status || '').toLowerCase();
  if (s === 'published') return 'published';
  if (s === 'closed') return 'closed';
  return 'draft';
};

export default function JobDetailsContent({ jobId, isModal = false, onClose, onJobLoaded }: JobDetailsContentProps) {
  const router = useRouter();
  const { user } = useAuth();
  // Active tab in the JD viewer (details | post | pipeline | screening | history)
  const [tab, setTab] = useState<'details' | 'post' | 'pipeline' | 'screening' | 'history'>('details');
  // Rationale viewer popup (AI Candidate Screening → eye icon)
  const [rationaleView, setRationaleView] = useState<{ name: string; text: string } | null>(null);
  const [job, setJob] = useState<JobDetail | null>(null);
  const [postings, setPostings] = useState<Posting[]>([]);
  const [audit, setAudit] = useState<{ id: number; action: string; changes: string; by: string | null; created_at: string }[]>([]);
  const [rankRun, setRankRun] = useState<any | null>(null);
  const [rankScores, setRankScores] = useState<any[]>([]);
  const [ranking, setRanking] = useState(false);
  const [showWeights, setShowWeights] = useState(false);
  // Screening selection: which ranked candidates are checked (keyed by
  // candidate_id). Feeds the toolbar's bulk Email/SMS/WhatsApp send actions.
  const [emailSel, setEmailSel] = useState<Record<string, boolean>>({});
  // Which bulk action is currently sending (label), or null when idle.
  const [bulkSending, setBulkSending] = useState<string | null>(null);
  // Selected notification type in the dropdown menu.
  const [notifType, setNotifType] = useState<string>('Send Email');
  // Preview & edit modal — shown before any bulk notification actually goes
  // out, pre-filled with the default template (editable before sending).
  const [previewOpen, setPreviewOpen] = useState(false);
  const [previewSubject, setPreviewSubject] = useState('');
  const [previewMessage, setPreviewMessage] = useState('');
  const [templates, setTemplates] = useState<EmailTemplate[]>([]);
  const [templateId, setTemplateId] = useState<number | ''>('');

  useEffect(() => {
    fetchEmailTemplates().then((rows) => {
      setTemplates(rows);
      if (rows.length) setTemplateId(rows[0].id);
    });
  }, []);
  // Sub-tabs INSIDE the AI Candidate Screening section.
  const [screeningTab, setScreeningTab] = useState<'ranking' | 'notifications' | 'calls'>('ranking');
  // ── Bulk AI calling (reuses the existing POST /ai-calls/start/ one candidate
  // at a time, so progress is real and no new calling logic is introduced).
  const [callProgress, setCallProgress] = useState<{
    total: number; processed: number; completed: number; failed: number;
    currentName: string; done: boolean;
  } | null>(null);
  const [bulkCalling, setBulkCalling] = useState(false);
  // ── Call History tab
  const [callHistory, setCallHistory] = useState<CallHistoryRow[]>([]);
  const [callHistoryLoading, setCallHistoryLoading] = useState(false);
  const [callSummaryRow, setCallSummaryRow] = useState<CallHistoryRow | null>(null);
  const [callSummary, setCallSummary] = useState<CallSummaryData | null>(null);
  const [callSummaryLoading, setCallSummaryLoading] = useState(false);
  // Notification history for this JD (from the existing NotificationLog) + live
  // progress of an in-flight bulk run (updated per batch, no page refresh).
  const [notifLogs, setNotifLogs] = useState<any[]>([]);
  const [notifLoading, setNotifLoading] = useState(false);
  const [bulkProgress, setBulkProgress] = useState<{
    action: string; total: number; processed: number;
    sent: number; failed: number;
    /** Per-channel sent tallies, e.g. { EMAIL: 12, SMS: 10, WHATSAPP: 9 }. */
    perChannel: Record<string, number>;
    inFlightIds: number[]; pendingIds: number[];
  } | null>(null);
  const [rankParams, setRankParams] = useState<{ key: string; label: string; weight: number }[]>([]);
  const [weights, setWeights] = useState<Record<string, number>>({});
  const [loading, setLoading] = useState(true);
  const [selected, setSelected] = useState<string[]>([]);
  const [posting, setPosting] = useState(false);
  const [retrying, setRetrying] = useState<string | null>(null);
  const [publishStep, setPublishStep] = useState<'preview' | 'confirm' | 'results' | null>(null);
  const [publishChannels, setPublishChannels] = useState<string[]>([]);
  const [publishResults, setPublishResults] = useState<PublishResult[]>([]);
  // Read-only pre-publish preview of the application URL per platform.
  const [previewLinks, setPreviewLinks] = useState<PreviewLink[] | null>(null);
  const [previewLoading, setPreviewLoading] = useState(false);
  const [copiedLink, setCopiedLink] = useState<string | null>(null);
  const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);

  // Q&A Edit States
  const [isEditingQA, setIsEditingQA] = useState(false);
  const [editableQuestions, setEditableQuestions] = useState<{ question: string; answer: string }[]>([]);
  const [savingQA, setSavingQA] = useState(false);

  const isAdmin = user?.role === 'ADMIN';
  const canPost = isAdmin
    || (user?.role ?? '').toUpperCase().includes('MANAGER')
    || user?.permissions?.includes('jobs.add_jobposting')
    || false;
  const hasChangePermission = isAdmin || user?.permissions?.includes('jobs.change_jobdescription') || false;
  // Once a JD is Published it becomes read-only: neither the JD fields nor its
  // screening Q&A can be edited from the viewer anymore.
  const jdPublished = job ? canonicalStatus(job) === 'published' : false;
  // "Rank Candidates" is gated by a dedicated permission (pipeline.rank_candidates)
  // instead of an Admin-only check. ADMIN keeps access (holds every permission).
  const canRank = isAdmin || user?.permissions?.includes('pipeline.rank_candidates') || false;
  // Bulk Email/SMS/WhatsApp actions mirror the backend notify permission
  // (IsRecruiterOrAdmin = admin / any *MANAGER* / recruiter with candidates.view_candidate),
  // so the UI never shows an action the API would reject. The backend still enforces it.
  const canNotify = isAdmin
    || (user?.role ?? '').toUpperCase().includes('MANAGER')
    || ((user?.role ?? '').toUpperCase() === 'RECRUITER' && (user?.permissions?.includes('candidates.view_candidate') ?? false))
    || false;
  // Mirrors the backend's CanRunScreening permission (admin / recruiter / any
  // *MANAGER*) — the same rule the single-candidate Call button uses. Not a new
  // permission: the API enforces it regardless of what this returns.
  const canCall = isAdmin
    || (user?.role ?? '').toUpperCase() === 'RECRUITER'
    || (user?.role ?? '').toUpperCase().includes('MANAGER')
    || false;

  const loadStatus = useCallback(async () => {
    try {
      const res = (await api.get(`/jobs/${jobId}/posting-status/`)) as { data?: Posting[] };
      setPostings(Array.isArray(res.data) ? res.data : []);
    } catch {
      /* status is non-critical */
    }
  }, [jobId]);

  const loadAudit = useCallback(async () => {
    try {
      const res = (await api.get(`/jobs/${jobId}/audit-log/`)) as any;
      setAudit(Array.isArray(res.data) ? res.data : []);
    } catch { setAudit([]); }
  }, [jobId]);

  const loadRanking = useCallback(async () => {
    try {
      const res = (await api.get(`/pipeline/rank/${jobId}/`)) as any;
      setRankRun(res?.data?.run ?? null);
      setRankScores(res?.data?.scores ?? []);
    } catch { setRankRun(null); setRankScores([]); }
  }, [jobId]);

  const [pipelineCount, setPipelineCount] = useState<number | null>(null);
  useEffect(() => {
    const statusLower = (job?.status || '').toLowerCase();
    if (statusLower !== 'published') return;
    api.get(`/pipeline/applications/?job=${jobId}&page_size=1`)
      .then((r: any) => {
        const d = r?.data ?? r;
        setPipelineCount(typeof d?.count === 'number' ? d.count : (Array.isArray(d) ? d.length : (d?.results?.length ?? 0)));
      })
      .catch(() => setPipelineCount(null));
  }, [job?.status, jobId]);

  // Candidate list for the Pipeline tab — fetched lazily when that tab is opened.
  const [pipelineApps, setPipelineApps] = useState<any[] | null>(null);
  const [pipelineAppsLoading, setPipelineAppsLoading] = useState(false);
  useEffect(() => {
    if (tab !== 'pipeline' || pipelineApps !== null) return;
    if ((job?.status || '').toLowerCase() !== 'published') { setPipelineApps([]); return; }
    setPipelineAppsLoading(true);
    api.get(`/pipeline/applications/?job=${jobId}&page_size=200`)
      .then((r: any) => {
        const d = r?.data ?? r;
        setPipelineApps(Array.isArray(d) ? d : (d?.results ?? []));
      })
      .catch(() => setPipelineApps([]))
      .finally(() => setPipelineAppsLoading(false));
  }, [tab, job?.status, jobId, pipelineApps]);

  // Pipeline stages — for the "Stages" filter dropdown on the Candidate Pipeline
  // tab, sourced from Master Data > Stages (same active-stages list used by the
  // Kanban pipeline board). Fetched once, lazily, when the tab is opened.
  const [pipelineStages, setPipelineStages] = useState<{ id: number; name: string }[]>([]);
  useEffect(() => {
    if (tab !== 'pipeline' || pipelineStages.length > 0) return;
    api.get('/pipeline/stages/')
      .then((r: any) => {
        const d = r?.data ?? r;
        setPipelineStages(Array.isArray(d) ? d : []);
      })
      .catch(() => setPipelineStages([]));
  }, [tab, pipelineStages.length]);

  const pipelineFilters = useMemo(() => [
    {
      columnId: 'stage_name', title: 'Stage',
      placeholder: 'All Stages',
      options: pipelineStages.map((s) => ({ label: s.name, value: s.name })),
    },
  ], [pipelineStages]);

  const pipelineColumns = useMemo<ColumnDef<any, any>[]>(() => [
    {
      id: 'srNo', header: 'S.No.', enableSorting: false,
      cell: ({ row, table }: any) => {
        const { pageIndex, pageSize } = table.getState().pagination;
        return <span className="text-slate-400 dark:text-slate-500">{pageIndex * pageSize + row.index + 1}</span>;
      },
    },
    {
      accessorKey: 'candidate_name', header: 'Candidate',
      cell: ({ row }: any) => (
        <button
          onClick={() => router.push(`/candidates/${row.original.candidate}`)}
          title="View candidate details"
          className="font-bold text-[#405189] hover:underline dark:text-indigo-300 cursor-pointer text-left"
        >
          {row.original.candidate_name || `${row.original.candidate_first_name || ''} ${row.original.candidate_last_name || ''}`.trim() || '—'}
        </button>
      ),
    },
    { accessorKey: 'candidate_location', header: 'Location', cell: ({ row }: any) => row.original.candidate_location || <span className="text-slate-400">—</span> },
    {
      accessorKey: 'stage_name', header: 'Stage', filterFn: 'equalsString',
      cell: ({ row }: any) => (
        <span className="inline-flex px-2 py-0.5 text-[10px] font-bold border border-[#405189]/20 bg-[#405189]/10 text-[#405189] dark:text-indigo-300 whitespace-nowrap">
          {row.original.stage_name || '—'}
        </span>
      ),
    },
    { accessorKey: 'score', header: 'Score', cell: ({ row }: any) => (row.original.score != null ? row.original.score : 0) },
    {
      id: 'source',
      header: 'Source',
      cell: ({ row }: any) => {
        // self_applied still implies DIRECT — that inference is unchanged.
        const s = row.original.candidate_source || (row.original.self_applied ? 'DIRECT' : '');
        // No meaningful attribution (OTHER / null / blank) → plain dash, styled
        // like the Stage column's empty state instead of an "Other" badge.
        if (isSourceEmpty(s)) {
          return <span className="text-slate-500 dark:text-slate-400">{SOURCE_EMPTY}</span>;
        }
        return (
          <span className="inline-flex px-2 py-0.5 text-[10px] font-bold border border-indigo-500/20 bg-indigo-500/10 text-indigo-600 dark:text-indigo-300 whitespace-nowrap">
            {sourceLabel(s)}
          </span>
        );
      },
    },
    { accessorKey: 'created_at', header: 'Added on', cell: ({ row }: any) => formatDateTime(row.original.created_at) },
  ], [router]);

  const rankColumns = useMemo<ColumnDef<any, any>[]>(() => [
    // Selection checkbox column (with Select-All header) — shown to users who
    // can send notifications (they drive the bulk Email/SMS/WhatsApp actions).
    ...(canNotify ? [{
      id: 'select',
      enableSorting: false,
      header: ({ table }: any) => (
        <input
          type="checkbox"
          aria-label="Select all candidates"
          className="w-4 h-4 accent-[#405189] cursor-pointer align-middle"
          checked={table.getIsAllRowsSelected()}
          ref={(el: HTMLInputElement | null) => { if (el) el.indeterminate = table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected(); }}
          onChange={table.getToggleAllRowsSelectedHandler()}
        />
      ),
      cell: ({ row }: any) => (
        <input
          type="checkbox"
          aria-label="Select candidate"
          className="w-4 h-4 accent-[#405189] cursor-pointer align-middle"
          checked={row.getIsSelected()}
          onChange={row.getToggleSelectedHandler()}
        />
      ),
    }] : []),
    {
      accessorKey: 'candidate_name', header: 'Candidate',
      cell: ({ row }: any) => (
        <span className="font-bold text-slate-700 dark:text-slate-200 whitespace-nowrap">
          {row.original.candidate_name}
          {row.original.is_top && <span className="ml-1.5 text-[9px] font-extrabold px-1.5 py-0.5 bg-amber-100 text-amber-700">TOP</span>}
        </span>
      ),
    },
    {
      accessorKey: 'candidate_email', header: 'Email',
      cell: ({ row }: any) => (
        row.original.candidate_email
          ? <a href={`mailto:${row.original.candidate_email}`} className="text-[#405189] dark:text-indigo-400 hover:underline whitespace-nowrap">{row.original.candidate_email}</a>
          : <span className="text-slate-400">—</span>
      ),
    },
    {
      accessorKey: 'candidate_mobile', header: 'Mobile',
      cell: ({ row }: any) => (
        row.original.candidate_mobile
          ? <a href={`tel:${row.original.candidate_mobile}`} className="text-slate-700 dark:text-slate-300 hover:underline whitespace-nowrap">{row.original.candidate_mobile}</a>
          : <span className="text-slate-400">—</span>
      ),
    },
    {
      accessorKey: 'effective_score', header: 'Score',
      cell: ({ row }: any) => {
        const s = row.original;
        return (
          <div>
            <span className={`inline-flex items-center justify-center min-w-[36px] px-2 py-0.5 font-extrabold ${s.effective_score >= 75 ? 'bg-emerald-50 text-emerald-700' : s.effective_score >= 50 ? 'bg-amber-50 text-amber-700' : 'bg-rose-50 text-rose-700'}`}>
              {s.effective_score}
            </span>
            {s.manual_override != null && (
              <button onClick={() => isAdmin && overrideScore(s.id)} className="block text-[9px] text-slate-450 hover:underline mt-0.5 text-left cursor-pointer" title={s.override_reason}>overridden</button>
            )}
          </div>
        );
      },
    },
    ...rankParams.map((p) => ({
      id: `p_${p.key}`, header: p.label.split(' ')[0],
      cell: ({ row }: any) => row.original.parameter_scores?.[p.key] ?? '—',
    })),
    {
      id: 'rationale', header: 'Rationale',
      cell: ({ row }: any) => (
        row.original.rationale
          ? (
            <button
              type="button"
              title={row.original.rationale}
              onClick={() => setRationaleView({ name: row.original.candidate_name, text: row.original.rationale })}
              className="w-7 h-7 inline-flex items-center justify-center border border-slate-200 dark:border-slate-700 text-slate-500 hover:text-white hover:bg-[#405189] transition cursor-pointer"
            >
              <i className="fa-regular fa-eye text-xs" />
            </button>
          )
          : <span className="text-slate-400">—</span>
      ),
    },
    {
      accessorKey: 'email_status', header: 'Email Status',
      cell: ({ row }: any) => {
        const st = row.original.email_status || 'NOT_SENT';
        const sentAt = row.original.email_sent_at;
        const cfg: Record<string, [string, string]> = {
          SENT: ['Sent', 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300'],
          FAILED: ['Failed', 'bg-rose-50 text-rose-700 dark:bg-rose-950/40 dark:text-rose-300'],
          NOT_SENT: ['Not Sent', 'bg-slate-100 text-slate-500 dark:bg-slate-800 dark:text-slate-400'],
        };
        const [label, cls] = cfg[st] || cfg.NOT_SENT;
        return (
          <span
            title={st === 'SENT' && sentAt ? `Sent on ${formatDateTime(sentAt)}` : undefined}
            className={`inline-flex px-2 py-0.5 text-[10px] font-bold whitespace-nowrap ${cls}`}
          >
            {st === 'SENT' && <i className="fa-solid fa-circle-check mr-1 text-[9px]" />}{label}
          </span>
        );
      },
    },
  ], [rankParams, isAdmin, canNotify]);

  const shareShortlist = async () => {
    try {
      const appsRes = (await api.get(`/pipeline/applications/?job=${jobId}&page_size=200`)) as any;
      const apps = appsRes?.data?.results ?? appsRes?.data ?? [];
      const ids = apps.map((a: any) => a.id);
      if (!ids.length) { toast.warning('No candidates in this JD pipeline to shortlist.'); return; }
      const r = (await api.post('/pipeline/shortlists/', { job: Number(jobId), application_ids: ids })) as any;
      const url = r?.data?.url || '';
      try { await navigator.clipboard.writeText(url); toast.success('Customer shortlist link created & copied!'); }
      catch { toast.success('Customer shortlist link created.'); }
    } catch (e) { toast.error(e instanceof Error ? e.message : 'Could not create shortlist'); }
  };

  const runRanking = async () => {
    const total = Object.values(weights).reduce((a, b) => a + Number(b || 0), 0);
    if (total !== 100) { toast.warning(`Weights must total 100 (currently ${total}).`); return; }
    setRanking(true);
    try {
      const res = (await api.post(`/pipeline/rank/${jobId}/`, { top_n: 5, weights })) as any;
      toast.success(res?.message || 'Ranking complete');
      await loadRanking();
    } catch (e) {
      toast.error(e instanceof Error ? e.message : 'Ranking failed');
    } finally { setRanking(false); }
  };

  const overrideScore = async (scoreId: number) => {
    const val = window.prompt('New score (0-100):');
    if (val === null) return;
    const reason = window.prompt('Reason for override (required):');
    if (!reason || !reason.trim()) { toast.warning('A reason is required.'); return; }
    try {
      await api.patch(`/pipeline/rank-score/${scoreId}/`, { score: Number(val), reason: reason.trim() });
      toast.success('Score overridden');
      await loadRanking();
    } catch (e) {
      toast.error(e instanceof Error ? e.message : 'Override failed');
    }
  };

  // Candidate ids currently checked in the ranked table (keys map to candidate_id).
  const selectedEmailIds = Object.keys(emailSel).filter((k) => emailSel[k]).map(Number);

  const rankedCandidateIds = useMemo(
    () => rankScores.map((s) => Number(s.candidate_id)).filter(Boolean),
    [rankScores],
  );

  // Notification history — reads the EXISTING notification log
  // (GET /candidates/notifications/) and narrows it to this JD's screened
  // candidates. NotificationLog has no JD column, so candidate-scoping is how
  // rows are attributed to this JD without touching the schema or the API.
  const loadNotifLogs = useCallback(async (candidateIds: number[]) => {
    if (candidateIds.length === 0) { setNotifLogs([]); return; }
    setNotifLoading(true);
    try {
      const r = (await api.get('/candidates/notifications/')) as any;
      const all: any[] = r?.data ?? r ?? [];
      const idSet = new Set(candidateIds.map(Number));
      setNotifLogs(all.filter((l) => idSet.has(Number(l.candidate_id))));
    } catch {
      setNotifLogs([]);
    } finally {
      setNotifLoading(false);
    }
  }, []);

  /** Call History — one row per AI call for this JD.
   *  `sync=1` makes the backend re-fetch the CURRENT state from Hunar for any
   *  call still in flight before responding, so a finished call can never keep
   *  showing Dialing. Terminal calls are not re-fetched. Both the Refresh button
   *  and the auto-refresh use this same path. */
  const loadCallHistory = useCallback(async () => {
    setCallHistoryLoading(true);
    try {
      const res = (await api.get(`/ai-calls/history/?job=${jobId}&sync=1`)) as any;
      setCallHistory(res?.data?.rows ?? []);
    } catch {
      setCallHistory([]);   // non-critical: the tab shows its empty state
    } finally {
      setCallHistoryLoading(false);
    }
  }, [jobId]);

  // Load history when the Send History sub-tab is opened.
  useEffect(() => {
    if (tab !== 'screening' || screeningTab !== 'notifications') return;
    loadNotifLogs(rankedCandidateIds);
  }, [tab, screeningTab, rankedCandidateIds, loadNotifLogs]);

  // Load the call log when the Call History sub-tab is opened.
  useEffect(() => {
    if (tab !== 'screening' || screeningTab !== 'calls') return;
    loadCallHistory();
  }, [tab, screeningTab, loadCallHistory]);

  // While any listed call is still in flight, refresh so the status column tracks
  // what Hunar reports. Stops as soon as nothing is running.
  useEffect(() => {
    if (tab !== 'screening' || screeningTab !== 'calls') return;
    const running = callHistory.some((r) => ['QUEUED', 'DIALING', 'IN_PROGRESS'].includes(r.status));
    if (!running) return;
    const t = setInterval(loadCallHistory, 5000);
    return () => clearInterval(t);
  }, [tab, screeningTab, callHistory, loadCallHistory]);

  // Map an action label -> the channels the existing notify-bulk API sends on.
  // Declared before the memos below that reference it.
  const CHANNELS_FOR: Record<string, string[]> = {
    'Send WhatsApp, SMS & Email': ['EMAIL', 'WHATSAPP', 'SMS'],
    'Send WhatsApp': ['WHATSAPP'],
    'Send Email': ['EMAIL'],
    'Send SMS': ['SMS'],
  };

  /** History rows: logged attempts (Success/Failed) plus live Sending/Pending
   *  rows for candidates still being processed in the current run. */
  const notifRows = useMemo(() => {
    const jdCode = job?.jd_code || `JD-${String(job?.id ?? jobId).padStart(4, '0')}`;
    const jdTitle = job?.title || '';
    const logged = notifLogs.map((l) => ({
      key: `log-${l.id}`,
      candidate_name: l.candidate_name || '—',
      email: l.email || '',
      phone: l.phone || '',
      channel: l.channel,
      jd_code: jdCode,
      jd_title: jdTitle,
      sent_by: l.sent_by || '—',
      created_at: l.created_at,
      status: l.status,               // existing log values: SENT / FAILED
      error: l.error || '',
    }));

    if (bulkSending && bulkProgress) {
      const byId = new Map(rankScores.map((s) => [Number(s.candidate_id), s]));
      const chLabel = (CHANNELS_FOR[bulkProgress.action] || ['EMAIL']).join(' + ');
      const live = (ids: number[], status: string) => ids.map((id) => {
        const s = byId.get(id);
        return {
          key: `${status}-${id}`,
          candidate_name: s?.candidate_name || `Candidate #${id}`,
          email: s?.candidate_email || '',
          phone: s?.candidate_mobile || '',
          channel: chLabel,
          jd_code: jdCode,
          jd_title: jdTitle,
          sent_by: user?.full_name || user?.email || '—',
          created_at: '',
          status,
          error: '',
        };
      });
      return [
        ...live(bulkProgress.inFlightIds, 'SENDING'),
        ...live(bulkProgress.pendingIds, 'PENDING'),
        ...logged,
      ];
    }
    return logged;
  }, [notifLogs, bulkSending, bulkProgress, rankScores, user, job, jobId]);

  /** Call History table. Score/recommendation are already withheld server-side
   *  for calls that never completed, so a dash here means "no result", not
   *  "missing data". */
  const callHistoryColumns = useMemo<ColumnDef<any, any>[]>(() => [
    { accessorKey: 'candidate_name', header: 'Candidate',
      cell: ({ row }: any) => <span className="font-semibold text-slate-700 dark:text-slate-200">{row.original.candidate_name || '—'}</span> },
    { accessorKey: 'mobile_number', header: 'Mobile',
      cell: ({ getValue }: any) => getValue() || '—' },
    { accessorKey: 'job_title', header: 'Job Description',
      cell: ({ getValue }: any) => getValue() || '—' },
    { accessorKey: 'started_at', header: 'Call Date & Time',
      cell: ({ getValue }: any) => getValue() || '—' },
    { accessorKey: 'recruiter', header: 'Recruiter',
      cell: ({ getValue }: any) => getValue() || '—' },
    { accessorKey: 'ai_agent', header: 'AI Agent',
      cell: ({ row }: any) => {
        const id = row.original.ai_agent as string | undefined;
        const name = row.original.ai_agent_name as string | undefined;
        const code = row.original.ai_agent_code as string | undefined;
        if (name) {
          // Real agent name from Hunar; the id stays available on hover.
          return (
            <span title={id || undefined} className="inline-flex flex-col leading-tight">
              <span className="text-xs font-bold text-slate-700 dark:text-slate-200">{name}</span>
              {code && <span className="text-[10px] text-slate-400 font-mono">{code}</span>}
            </span>
          );
        }
        // No name resolved — say so rather than showing a bare UUID fragment.
        return (
          <span title={id || undefined} className="text-[11px] italic text-slate-400">
            AI Agent information unavailable
          </span>
        );
      } },
    { accessorKey: 'status', header: 'Call Status',
      cell: ({ row }: any) => {
        const b = CALL_STATUS_BADGES[row.original.status];
        return b ? (
          <span title={row.original.error_message || b.label}
            className={`inline-flex items-center gap-1 text-[10px] font-bold px-2 py-0.5 ${b.cls}`}>
            <i className={`fa-solid ${b.icon} ${b.pulse ? 'animate-pulse' : ''} text-[9px]`} />{b.label}
          </span>
        ) : <span className="text-slate-400">{row.original.status || '—'}</span>;
      } },
    { accessorKey: 'duration', header: 'Duration',
      cell: ({ getValue }: any) => {
        const s = getValue() as number | null;
        if (s == null) return '—';
        const m = Math.floor(s / 60);
        return m > 0 ? `${m}m ${s % 60}s` : `${s}s`;
      } },
    { accessorKey: 'score', header: 'Score',
      cell: ({ getValue }: any) => (getValue() == null ? '—' : getValue()) },
    { accessorKey: 'recommendation', header: 'Recommendation',
      cell: ({ getValue }: any) => {
        const r = getValue() as string;
        const map: Record<string, string> = {
          QUALIFIED: 'text-emerald-600 dark:text-emerald-400',
          REVIEW: 'text-amber-600 dark:text-amber-400',
          NOT_QUALIFIED: 'text-rose-600 dark:text-rose-400',
        };
        const label: Record<string, string> = {
          QUALIFIED: 'Qualified', REVIEW: 'Needs Review', NOT_QUALIFIED: 'Not Qualified',
        };
        return r ? <span className={`font-bold ${map[r] ?? ''}`}>{label[r] ?? r}</span> : '—';
      } },
    { accessorKey: 'updated_at', header: 'Last Updated',
      cell: ({ getValue }: any) => getValue() || '—' },
    {
      id: 'call_summary',
      header: 'Call Summary',
      cell: ({ row }: any) => {
        const r = row.original as CallHistoryRow;
        return (
          <button
            type="button"
            onClick={() => openCallSummary(r)}
            disabled={!r.is_completed}
            title={r.is_completed
              ? 'View the AI call summary'
              : `No call summary available because the call was not completed (${CALL_STATUS_BADGES[r.status]?.label ?? r.status}).`}
            className="inline-flex items-center gap-1.5 border border-[#405189]/30 text-[#405189] dark:text-indigo-300 hover:bg-[#405189]/10 disabled:opacity-40 disabled:cursor-not-allowed text-[10px] font-bold px-2 py-1 transition cursor-pointer whitespace-nowrap"
          >
            <i className="fa-solid fa-file-lines text-[9px]" />Call Summary
          </button>
        );
      },
    },
  ], []);

  const callHistoryFilters = useMemo(() => [
    {
      columnId: 'status', title: 'Call Status',
      options: Object.entries(CALL_STATUS_BADGES).map(([value, meta]) => ({ label: meta.label, value })),
    },
  ], []);

  const notifColumns = useMemo<ColumnDef<any, any>[]>(() => [
    {
      accessorKey: 'candidate_name', header: 'Candidate',
      cell: ({ getValue }: any) => <span className="font-bold text-slate-800 dark:text-slate-200 whitespace-nowrap">{getValue() as string}</span>,
    },
    { accessorKey: 'email', header: 'Email', cell: ({ getValue }: any) => (getValue() as string) || <span className="text-slate-400">—</span> },
    { accessorKey: 'phone', header: 'Mobile', cell: ({ getValue }: any) => (getValue() as string) || <span className="text-slate-400">—</span> },
    {
      accessorKey: 'channel', header: 'Type', filterFn: 'equalsString',
      cell: ({ getValue }: any) => (
        <span className="font-semibold text-slate-700 dark:text-slate-300">
          {String(getValue() || '').split(' + ').map((c) => (
            <span key={c} className="inline-flex items-center gap-1 mr-1.5 whitespace-nowrap">
              <i className={c === 'WHATSAPP' ? 'fa-brands fa-whatsapp text-[#0ab39c]' : c === 'SMS' ? 'fa-solid fa-comment-sms text-[#299cdb]' : 'fa-solid fa-envelope text-[#405189]'} />
              {c === 'WHATSAPP' ? 'WhatsApp' : c === 'SMS' ? 'SMS' : 'Email'}
            </span>
          ))}
        </span>
      ),
    },
    {
      accessorKey: 'jd_code', header: 'JD Code',
      cell: ({ getValue }: any) => <span className="font-bold text-[#405189] dark:text-indigo-300 whitespace-nowrap">{(getValue() as string) || '—'}</span>,
    },
    { accessorKey: 'jd_title', header: 'JD Title', cell: ({ getValue }: any) => <span className="whitespace-nowrap">{(getValue() as string) || '—'}</span> },
    { accessorKey: 'sent_by', header: 'Sent By', cell: ({ getValue }: any) => (getValue() as string) || '—' },
    {
      accessorKey: 'created_at', header: 'Sent Date & Time',
      cell: ({ getValue }: any) => {
        const v = getValue() as string;
        return <span className="whitespace-nowrap text-slate-500 dark:text-slate-400">{v ? formatDateTime(v) : '—'}</span>;
      },
    },
    {
      accessorKey: 'status', header: 'Status', filterFn: 'equalsString',
      cell: ({ getValue }: any) => {
        const st = String(getValue() || '');
        // SENT/FAILED come from the existing NotificationLog (SENT shows as
        // "Success"); PENDING/SENDING are live states of the current run.
        const cfg: Record<string, [string, string, string]> = {
          PENDING: ['Pending', 'bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-300', 'fa-hourglass-half'],
          SENDING: ['Sending', 'bg-sky-50 text-sky-700 dark:bg-sky-950/40 dark:text-sky-300', 'fa-spinner fa-spin'],
          SENT:    ['Success', 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300', 'fa-circle-check'],
          FAILED:  ['Failed',  'bg-rose-50 text-rose-700 dark:bg-rose-950/40 dark:text-rose-300', 'fa-circle-exclamation'],
        };
        const [label, cls, icon] = cfg[st] || [st || '—', 'bg-slate-100 text-slate-500', 'fa-circle'];
        return <span className={`inline-flex items-center gap-1 px-2 py-0.5 text-[10px] font-bold whitespace-nowrap ${cls}`}><i className={`fa-solid ${icon} text-[9px]`} />{label}</span>;
      },
    },
    {
      accessorKey: 'error', header: 'Failure Reason',
      cell: ({ getValue }: any) => {
        const e = (getValue() as string) || '';
        return e
          ? <p title={e} className="text-rose-500 line-clamp-2 max-w-[220px] break-words">{e}</p>
          : <span className="text-slate-400">—</span>;
      },
    },
  ], []);

  const notifFilters = useMemo(() => [
    {
      columnId: 'channel', title: 'Type',
      options: [
        { label: 'Email', value: 'EMAIL' },
        { label: 'SMS', value: 'SMS' },
        { label: 'WhatsApp', value: 'WHATSAPP' },
      ],
    },
    {
      columnId: 'status', title: 'Status',
      options: [
        { label: 'Success', value: 'SENT' },
        { label: 'Failed', value: 'FAILED' },
        { label: 'Sending', value: 'SENDING' },
        { label: 'Pending', value: 'PENDING' },
      ],
    },
  ], []);

  // Default notification template — shown (and editable) in the Preview
  // modal before anything is sent. {{candidate_name}} is replaced per
  // recipient at send time so one edited template still personalises.
  const buildDefaultTemplate = useCallback(() => {
    const jobTitle = job?.title || 'the open position';
    const company = job?.client_name || 'our team';
    const tpl = templates.find((t) => t.id === templateId);
    if (tpl) {
      // Pre-fill job/company/location now; leave {{candidate_name}} for per-recipient.
      const vars = { job_title: jobTitle, company, location: job?.location || '' };
      return {
        subject: fillTemplate(tpl.subject || `Regarding your application for ${jobTitle}`, vars),
        message: fillTemplate(tpl.body, vars),
      };
    }
    return {
      subject: `Regarding your application for ${jobTitle}`,
      message: `Hi {{candidate_name}},\n\nWe would like to update you about your application for ${jobTitle} at ${company}.\n\nRegards,\nTA-ATS Team`,
    };
  }, [job, templates, templateId]);

  // Opens the Preview & Edit modal instead of sending immediately. The modal's
  // own Send button is what actually triggers sendNotificationsDirectly.
  // Kept intact (with the modal below) so the edit-before-send flow can be
  // restored; the toolbar now sends directly via sendSelectedNotifications.
  const openNotifyPreview = () => {
    const rows = rankScores.filter((s) => emailSel[String(s.candidate_id)]);
    if (rows.length === 0) { toast.warning('Select at least one candidate first.'); return; }
    const tpl = buildDefaultTemplate();
    setPreviewSubject(tpl.subject);
    setPreviewMessage(tpl.message);
    setPreviewOpen(true);
  };

  /** Toolbar "Send": dispatches on whatever the dropdown currently has selected
   *  (Send Email / Send SMS / Send WhatsApp / Send WhatsApp, SMS & Email — and
   *  any option added to CHANNELS_FOR later). Uses the same default template
   *  and the same sendNotificationsDirectly path as before, so validations,
   *  loaders, live progress, send history and the notify-bulk API are unchanged.
   *
   *  Messages go out to real candidates and cannot be recalled, so the send is
   *  confirmed first — the app's standard showConfirm, replacing the
   *  confirmation step the Preview modal used to provide. */
  /**
   * Bulk AI calling — reuses the EXISTING POST /ai-calls/start/ (the same endpoint
   * the single-candidate flow and the AI Screening page use), invoked once per
   * selected candidate so the calls go out sequentially and the progress shown is
   * the real per-candidate outcome. No separate calling implementation, no new
   * endpoint, and one failure never stops the rest.
   */
  const callSelectedCandidates = async () => {
    const rows = rankScores.filter((s) => emailSel[String(s.candidate_id)]);
    if (rows.length === 0) { toast.warning('Select at least one candidate first.'); return; }
    if (bulkCalling) return;

    const res = await showConfirm({
      title: 'Call Candidates',
      text: `Place AI screening calls to ${rows.length} selected candidate${rows.length === 1 ? '' : 's'}, one at a time?`,
      confirmText: `Call ${rows.length}`,
      icon: 'question',
    });
    if (!res.isConfirmed) return;

    setBulkCalling(true);
    let completed = 0;
    let failed = 0;
    setCallProgress({
      total: rows.length, processed: 0, completed: 0, failed: 0,
      currentName: '', done: false,
    });

    for (let i = 0; i < rows.length; i++) {
      const row = rows[i];
      const name = row.candidate_name || `Candidate #${row.candidate_id}`;
      setCallProgress({
        total: rows.length, processed: i, completed, failed,
        currentName: name, done: false,
      });
      try {
        const r = (await api.post('/ai-calls/start/', {
          job: Number(jobId),
          candidate_ids: [Number(row.candidate_id)],
        })) as { data?: { queued?: number; errors?: string[] } };
        const errs = r.data?.errors ?? [];
        if (r.data?.queued) {
          completed += 1;
        } else {
          failed += 1;
          errs.forEach((e) => toast.warn(e));
          if (!errs.length) toast.warn(`${name}: the call was not placed.`);
        }
      } catch (e) {
        // Keep going: the remaining candidates must still be attempted.
        failed += 1;
        toast.error(`${name}: ${e instanceof Error ? e.message : 'call failed'}`);
      }
      setCallProgress({
        total: rows.length, processed: i + 1, completed, failed,
        currentName: name, done: i + 1 === rows.length,
      });
    }

    setBulkCalling(false);
    if (completed) toast.success(`Started ${completed} call${completed === 1 ? '' : 's'} ✓`);
    if (failed) toast.warn(`${failed} call${failed === 1 ? '' : 's'} could not be started.`);
    await loadCallHistory();
    await loadRanking();
  };

  /** Open the summary Hunar already generated — never regenerated here. */
  const openCallSummary = async (row: CallHistoryRow) => {
    setCallSummaryRow(row);
    setCallSummary(null);
    setCallSummaryLoading(true);
    try {
      const r = (await api.get(`/ai-calls/${row.call_id}/transcript/`)) as any;
      setCallSummary(r?.data ?? null);
    } catch (e) {
      toast.error(e instanceof Error ? e.message : 'Could not load the call summary.');
    } finally {
      setCallSummaryLoading(false);
    }
  };

  const sendSelectedNotifications = async () => {
    const rows = rankScores.filter((s) => emailSel[String(s.candidate_id)]);
    if (rows.length === 0) { toast.warning('Select at least one candidate first.'); return; }
    if (bulkSending) return;

    const channelLabel = (CHANNELS_FOR[notifType] || ['EMAIL'])
      .map((c) => (c === 'WHATSAPP' ? 'WhatsApp' : c === 'SMS' ? 'SMS' : 'Email'))
      .join(' + ');
    const res = await showConfirm({
      title: notifType,
      text: `Send ${channelLabel} to ${rows.length} selected candidate${rows.length === 1 ? '' : 's'}?`,
      confirmText: 'Send',
      icon: 'question',
    });
    if (!res.isConfirmed) return;

    sendNotificationsDirectly(notifType, buildDefaultTemplate());
  };

  // Bulk-send to the selected candidates via the EXISTING notification service
  // (/candidates/notify-bulk/). Runs in the background: selections are chunked
  // and sent batch-by-batch with await, so the UI never freezes and no single
  // request is large enough to time out — even for 50–100+ candidates. Each
  // candidate is processed independently by the backend (one failure doesn't
  // stop the rest); a whole-batch network error is caught so remaining batches
  // still run. Every send is logged to the existing NotificationLog history.
  // `template` — when provided (from the Preview & Edit modal) — overrides the
  // default subject/message; {{candidate_name}} is substituted per recipient.
  const sendNotificationsDirectly = async (action: string, template?: { subject: string; message: string }) => {
    const rows = rankScores.filter((s) => emailSel[String(s.candidate_id)]);
    if (rows.length === 0) { toast.warning('Select at least one candidate first.'); return; }
    if (bulkSending) return;

    const tpl = template ?? buildDefaultTemplate();
    const channels = CHANNELS_FOR[action] || ['EMAIL'];
    const items = rows.map((s) => {
      const name = s.candidate_name || 'Candidate';
      const vars = { name, job_title: job?.title || '', company: job?.client_name || '', location: job?.location || '' };
      return {
        id: Number(s.candidate_id),
        subject: fillTemplate(tpl.subject, vars),
        message: fillTemplate(tpl.message, vars),
      };
    });

    const BATCH = 1;    // process candidate-by-candidate for live progress updates
    const allIds = items.map((it) => it.id);
    setBulkSending(action);
    let sent = 0, failed = 0;
    const perChannel: Record<string, number> = {};
    // Distinct candidates with at least one successful channel — the success
    // alert reports candidates, not per-channel sends (a combined send makes
    // up to 3 notifications per candidate).
    const okCandidateIds = new Set<number>();
    // Candidates skipped because they opted out of email (backend reports this
    // as "Skipped (Candidate Unsubscribed)") — lets the error popup say WHY.
    const unsubscribedIds = new Set<number>();
    // Seed the live loader (Pending for everything, nothing in flight yet).
    setBulkProgress({
      action, total: items.length, processed: 0, sent: 0, failed: 0,
      perChannel: {}, inFlightIds: [], pendingIds: allIds,
    });
    try {
      for (let i = 0; i < items.length; i += BATCH) {
        const batch = items.slice(i, i + BATCH);
        const batchIds = batch.map((it) => it.id);
        // Mark this batch as Sending; everything after it stays Pending.
        setBulkProgress({
          action, total: items.length, processed: i, sent, failed,
          perChannel: { ...perChannel }, inFlightIds: batchIds,
          pendingIds: allIds.slice(i + batch.length),
        });
        try {
          const res = (await api.post('/candidates/notify-bulk/', { channels, items: batch, job_id: jobId })) as any;
          const d = res?.data || {};
          const batchSent = d.sent ?? 0;
          const batchFailed = d.failed ?? 0;
          sent += batchSent;
          failed += batchFailed;

          // Update Email Status in rankScores state immediately per candidate
          (d.summary ?? []).forEach((row: any) => {
            const emailRes = (row.results ?? []).find((rr: any) => rr.channel === 'EMAIL');
            if (emailRes) {
              const newStatus = emailRes.status === 'SENT' ? 'SENT' : 'FAILED';
              setRankScores((prev) =>
                prev.map((s) => (Number(s.candidate_id) === Number(row.id) ? { ...s, email_status: newStatus } : s))
              );
            }
            (row.results ?? []).forEach((rr: any) => {
              if (rr.status === 'SENT') {
                perChannel[rr.channel] = (perChannel[rr.channel] || 0) + 1;
                okCandidateIds.add(Number(row.id));
              } else if (/unsubscrib/i.test(String(rr.error || ''))) {
                unsubscribedIds.add(Number(row.id));
              }
            });
          });
        } catch {
          // Whole-batch failure — count it and mark as FAILED
          failed += batch.length;
          setRankScores((prev) =>
            prev.map((s) => (batchIds.includes(Number(s.candidate_id)) ? { ...s, email_status: 'FAILED' } : s))
          );
        }
        const processed = Math.min(i + BATCH, items.length);
        setBulkProgress({
          action, total: items.length, processed, sent, failed,
          perChannel: { ...perChannel }, inFlightIds: [],
          pendingIds: allIds.slice(processed),
        });
        // Auto-update the Send History as each batch completes.
        loadNotifLogs(rankedCandidateIds);
      }
      // Re-read the persisted status from the backend so the Email Status
      // column reflects what was actually stored (never a hardcoded value).
      await loadRanking();
      const n = okCandidateIds.size;
      if (failed === 0) {
        showSuccess(`Notification sent successfully to ${n} candidate${n === 1 ? '' : 's'}.`, 'Email Sent');
      } else if (unsubscribedIds.size > 0) {
        // Same popup (title/icon/buttons/colours) — only the text explains that
        // the failure was an opt-out, not a delivery problem.
        showError(
          'Some selected candidate(s) have unsubscribed from email notifications. Emails were not sent to those candidates.',
          'Email Failed',
        );
      } else {
        showError('One or more emails could not be sent.', 'Email Failed');
      }
    } finally {
      setBulkSending(null);
      setBulkProgress(null);
      loadNotifLogs(rankedCandidateIds);
    }
  };

  useEffect(() => {
    if (!user) return;
    (async () => {
      try {
        const jobRes = (await api.get(`/jobs/${jobId}/`)) as any;
        setJob(jobRes.data);
        if (jobRes.data) {
          onJobLoaded?.(jobRes.data);
        }
        try {
          const pr = (await api.get('/pipeline/rank-parameters/')) as any;
          const params = (pr?.data ?? []).filter((p: any) => p.is_active);
          setRankParams(params.map((p: any) => ({ key: p.key, label: p.label, weight: p.weight })));
          const saved = jobRes.data?.rank_weights || {};
          const seed: Record<string, number> = {};
          params.forEach((p: any) => { seed[p.key] = saved[p.key] ?? p.weight; });
          setWeights(seed);
        } catch { /* optional */ }
        await loadStatus();
        await loadAudit();
        await loadRanking();
      } catch {
        /* job stays null */
      } finally {
        setLoading(false);
      }
    })();
  }, [user, jobId, loadStatus, loadAudit, loadRanking]);

  useEffect(() => {
    const hasPending = postings.some((p) => p.status === 'PENDING');
    if (hasPending && !pollRef.current) {
      pollRef.current = setInterval(loadStatus, 5000);
    } else if (!hasPending && pollRef.current) {
      clearInterval(pollRef.current);
      pollRef.current = null;
    }
    return () => {
      if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; }
    };
  }, [postings, loadStatus]);

  const postedChannels = new Set(postings.filter((p) => p.status === 'POSTED').map((p) => p.channel));
  const toggleChannel = (key: string) => {
    setSelected((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]));
  };

  const [forceRepost, setForceRepost] = useState(false);
  const [open, setOpen] = useState<Record<string, boolean>>({
    details: true, creative: false, publish: true, posting: true, ranking: false, history: false,
  });
  const toggle = (k: string) => setOpen((o) => ({ ...o, [k]: !o[k] }));

  const openPublishPreview = (channels: string[], viaRetry = false, force = false) => {
    if (channels.length === 0) return;
    if (viaRetry) setRetrying(channels[0]);
    setForceRepost(force);
    setPublishChannels(channels);
    setPublishResults([]);
    setPublishStep('preview');
    loadPreviewLinks();
  };

  /** Read-only preview of the application URL for every platform. The codes are
   *  stable per (JD, platform), so what is shown here is exactly what gets
   *  published. Failure is non-blocking — publishing never depends on it. */
  const loadPreviewLinks = async () => {
    setPreviewLinks(null);
    setCopiedLink(null);
    setPreviewLoading(true);
    try {
      const chs = CHANNELS.map((c) => c.key).join(',');
      const res = (await api.get(`/jobs/${jobId}/publish-preview/?channels=${chs}`)) as any;
      setPreviewLinks((res?.data?.links ?? []) as PreviewLink[]);
    } catch {
      setPreviewLinks(null);
    } finally {
      setPreviewLoading(false);
    }
  };

  const copyPreviewLink = async (link: PreviewLink) => {
    try {
      await navigator.clipboard.writeText(link.url);
      setCopiedLink(link.channel);
      setTimeout(() => setCopiedLink((c) => (c === link.channel ? null : c)), 1800);
    } catch {
      toast.info('Copy failed — select the link and copy it manually.');
    }
  };

  const togglePublishChannel = (key: string) => {
    setPublishChannels((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]));
  };

  const closePublishModal = () => {
    setPublishStep(null);
    setPublishChannels([]);
    setPublishResults([]);
    setRetrying(null);
    setPreviewLinks(null);
    setPreviewLoading(false);
    setCopiedLink(null);
  };

  const confirmPublish = async () => {
    setPosting(true);
    try {
      const res = (await api.post(`/jobs/${jobId}/post/`, { channels: publishChannels, force: forceRepost })) as any;
      const results: PublishResult[] = res?.data?.results ?? [];
      setPublishResults(results);
      setPublishStep('results');
      const ok = results.filter((r) => r.status === 'POSTED').length;
      if (ok) toast.success(`Published to ${ok} channel${ok > 1 ? 's' : ''} ✓`);
      setSelected([]);
      // loadStatus() refreshes `postings`, which flips the header status
      // shown on the results step (Draft → Published).
      await loadStatus();
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Posting failed');
      closePublishModal();
    } finally {
      setPosting(false);
      setRetrying(null);
    }
  };

  // Q&A Edit logic
  const startEditingQA = () => {
    const normalized = (job?.questions || []).map(q =>
      typeof q === 'string' ? { question: q, answer: '' } : { question: q?.question || '', answer: q?.answer || '' }
    );
    if (normalized.length === 0) {
      setEditableQuestions([{ question: '', answer: '' }]);
    } else {
      setEditableQuestions(normalized);
    }
    setIsEditingQA(true);
  };

  const saveQA = async () => {
    const cleanQuestions = editableQuestions
      .map(q => ({ question: (q.question || '').trim(), answer: (q.answer || '').trim() }))
      .filter(q => q.question);

    setSavingQA(true);
    try {
      await api.patch(`/jobs/${jobId}/`, { questions: cleanQuestions });
      toast.success('Questions & Answers updated successfully ✓');
      setJob(prev => prev ? { ...prev, questions: cleanQuestions } : null);
      setIsEditingQA(false);
      await loadAudit();
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'Could not save Q&A');
    } finally {
      setSavingQA(false);
    }
  };

  if (loading || !user) {
    return (
      <div className="flex items-center justify-center py-16 text-slate-500">
        <i className="fa-solid fa-spinner fa-spin mr-2"></i> Loading details...
      </div>
    );
  }

  if (!job) {
    return (
      <div className="flex items-center justify-center py-16 text-slate-500">
        You don't have permission to access this Job Description
      </div>
    );
  }

  const jobActive = (job.status || '').toLowerCase() === 'published';
  const badge = getStatusBadge(canonicalStatus(job));

  return (
    <>
      <main className={`flex-1 overflow-y-auto w-full ${isModal ? 'p-1' : 'p-4 sm:p-8'}`}>
        <div className="max-w-5xl mx-auto space-y-6">
          {!isModal && (
            <button
              onClick={() => router.push('/jobs')}
              className="text-xs font-bold text-slate-500 hover:text-[#405189] dark:text-slate-400 dark:hover:text-indigo-400 transition cursor-pointer flex items-center gap-1.5"
            >
              <i className="fa-solid fa-arrow-left" /> Back to Job Descriptions
            </button>
          )}

          {/* Tab bar */}
          <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-none shadow-sm overflow-x-auto">
            <div className="flex gap-1 p-1 min-w-max">
              {([
                ['details', 'JD Details', 'fa-file-lines'],
                ['post', 'Job Post & Publish', 'fa-paper-plane'],
                ['pipeline', `Candidate Pipeline${pipelineCount != null ? ` (${pipelineCount})` : ''}`, 'fa-diagram-project'],
                ['screening', 'AI Candidate Screening', 'fa-wand-magic-sparkles'],
                ['history', 'Change History', 'fa-clock-rotate-left'],
              ] as const).map(([key, label, icon]) => (
                <button
                  key={key}
                  onClick={() => setTab(key)}
                  className={`px-3 py-2 text-xs font-bold rounded-none transition whitespace-nowrap cursor-pointer ${tab === key
                    ? 'bg-[#405189] text-white'
                    : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'
                    }`}
                >
                  <i className={`fa-solid ${icon} mr-1.5`} />{label}
                </button>
              ))}
            </div>
          </div>

          {/* ===== Tab: JD Details ===== */}
          {tab === 'details' && (<>
            <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-none p-6 shadow-sm">
              <div className="flex flex-wrap items-start justify-between gap-3">
                <div className="min-w-0 flex-1">
                  <h2 className="text-xl font-bold text-slate-900 dark:text-white">{job.title}</h2>
                  <p className="text-xs text-slate-400 mt-1">
                    {job.client_name ? `Client: ${job.client_name}` : 'Internal Job'} · Created by{' '}
                    <span title={job.created_by_email} className="cursor-default font-semibold">{job.created_by_name || job.created_by_email}</span>
                  </p>
                </div>
                <div className="flex flex-wrap items-center gap-2">
                  {job.priority && (
                    <span className="text-xs px-2.5 py-1 rounded-full font-bold border bg-slate-50 dark:bg-slate-800 text-slate-600 dark:text-slate-300 border-slate-200 dark:border-slate-700">
                      {job.priority} Priority
                    </span>
                  )}
                  <span className={`text-xs px-2.5 py-1 rounded-full font-bold border ${badge.style}`}>
                    {badge.label}
                  </span>
                </div>
              </div>
              <div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mt-5 pt-5 border-t border-slate-100 dark:border-slate-800">
                {[
                  ['Location', job.location, 'fa-location-dot'],
                  ['Experience', job.experience_band || '—', 'fa-briefcase'],
                  ['CTC', job.ctc_band || '—', 'fa-money-bill-wave'],
                  ['Shift', job.shift || '—', 'fa-clock'],
                ].map(([label, value, icon]) => (
                  <div key={label as string}>
                    <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400">{label}</p>
                    <p className="text-sm font-semibold text-slate-800 dark:text-slate-200 mt-0.5">
                      <i className={`fa-solid ${icon} text-slate-400 mr-1.5 text-xs`} />{value}
                    </p>
                  </div>
                ))}
              </div>
            </div>

            <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-none p-6 shadow-sm">
              <button onClick={() => toggle('details')} className="w-full flex items-center justify-between mb-4 cursor-pointer">
                <h3 className="text-sm font-bold text-slate-800 dark:text-white">
                  <i className="fa-solid fa-file-lines text-[#405189] mr-2"></i>Job Description Details
                </h3>
                <i className={`fa-solid fa-chevron-down text-slate-400 text-xs transition-transform ${open.details ? 'rotate-180' : ''}`}></i>
              </button>
              {open.details && (<>
                <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-6 gap-y-3">
                  {([
                    ['Department', job.department],
                    ['Notice Period', job.notice_period],
                    ['Working Days', job.working_days],
                    ['No. of Positions', job.num_positions != null ? String(job.num_positions) : ''],
                    ['Certification', job.certification],
                    ['Priority', job.priority],
                  ] as [string, string | undefined][]).map(([label, value]) => (
                    <div key={label}>
                      <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400">{label}</p>
                      <p className="text-sm font-semibold text-slate-700 dark:text-slate-200 mt-0.5">{value || '—'}</p>
                    </div>
                  ))}
                </div>

                {job.qualifications && (
                  <div className="mt-4">
                    <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mb-1">Qualifications</p>
                    <p className="text-sm text-slate-700 dark:text-slate-300 whitespace-pre-line">{cleanHtmlText(job.qualifications)}</p>
                  </div>
                )}

                {(job.must_have_skills || job.good_to_have_skills) && (
                  <div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
                    {job.must_have_skills && (
                      <div>
                        <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mb-1.5">Must-Have Skills</p>
                        <div className="flex flex-wrap gap-1.5">
                          {cleanHtmlText(job.must_have_skills).split(',').map((s) => s.trim()).filter(Boolean).map((s) => (
                            <span key={s} className="text-[11px] font-semibold bg-[#405189]/10 text-[#405189] dark:bg-indigo-950/40 dark:text-indigo-300 px-2 py-0.5">{s}</span>
                          ))}
                        </div>
                      </div>
                    )}
                    {job.good_to_have_skills && (
                      <div>
                        <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mb-1.5">Good-to-Have Skills</p>
                        <div className="flex flex-wrap gap-1.5">
                          {cleanHtmlText(job.good_to_have_skills).split(',').map((s) => s.trim()).filter(Boolean).map((s) => (
                            <span key={s} className="text-[11px] font-semibold bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-300 px-2 py-0.5">{s}</span>
                          ))}
                        </div>
                      </div>
                    )}
                  </div>
                )}

                {job.work_details && (
                  <div className="mt-4">
                    <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mb-1">Work Details & Requirements</p>
                    <p className="text-sm text-slate-700 dark:text-slate-300 leading-relaxed whitespace-pre-line">
                      {cleanHtmlText(job.work_details)}
                    </p>
                  </div>
                )}

                {/* Screening Questions & Answers */}
                <div className="mt-4 border-t border-slate-100 dark:border-slate-800 pt-4">
                  <div className="flex items-center justify-between mb-3">
                    <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400">Screening Questions & Answers</p>
                    {hasChangePermission && !isEditingQA && (
                      <button
                        type="button"
                        onClick={() => { if (!jdPublished) startEditingQA(); }}
                        disabled={jdPublished}
                        title={jdPublished ? 'This JD is published and cannot be edited' : undefined}
                        className={`text-xs border px-2.5 py-1 font-bold transition flex items-center gap-1 ${jdPublished
                          ? 'bg-slate-50 dark:bg-slate-900 border-slate-200 dark:border-slate-800 text-slate-300 dark:text-slate-600 cursor-not-allowed'
                          : 'bg-slate-50 hover:bg-slate-100 dark:bg-slate-800 dark:hover:bg-slate-700 border-slate-200 dark:border-slate-800 text-[#405189] dark:text-indigo-400 cursor-pointer'
                          }`}
                      >
                        <i className="fa-solid fa-pen-to-square text-[10px]"></i>
                        {Array.isArray(job.questions) && job.questions.length > 0 ? 'Edit Q&A' : 'Add Q&A'}
                      </button>
                    )}
                  </div>

                  {isEditingQA ? (
                    <div className="space-y-4">
                      {editableQuestions.map((q, index) => (
                        <div key={index} className="p-3 border border-slate-200 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/50 relative">
                          <button
                            type="button"
                            onClick={() => setEditableQuestions(prev => prev.filter((_, idx) => idx !== index))}
                            className="absolute top-2 right-2 text-rose-500 hover:text-rose-700 transition cursor-pointer"
                            title="Remove question"
                          >
                            <i className="fa-solid fa-trash text-xs"></i>
                          </button>
                          <div className="space-y-2 pr-6">
                            <div>
                              <label className="block text-[10px] uppercase font-bold text-slate-400 mb-1">Question {index + 1}</label>
                              <input
                                type="text"
                                value={q.question}
                                onChange={(e) => setEditableQuestions(prev => prev.map((item, idx) => idx === index ? { ...item, question: e.target.value } : item))}
                                placeholder="e.g. Do you have experience with React?"
                                className="w-full bg-white dark:bg-slate-950 border border-slate-200 dark:border-slate-800 px-3 py-1.5 text-xs text-slate-800 dark:text-slate-200 focus:outline-none"
                              />
                            </div>
                            <div>
                              <label className="block text-[10px] uppercase font-bold text-slate-400 mb-1">Expected Answer (Optional)</label>
                              <input
                                type="text"
                                value={q.answer}
                                onChange={(e) => setEditableQuestions(prev => prev.map((item, idx) => idx === index ? { ...item, answer: e.target.value } : item))}
                                placeholder="e.g. Yes, 3+ years"
                                className="w-full bg-white dark:bg-slate-950 border border-slate-200 dark:border-slate-800 px-3 py-1.5 text-xs text-slate-800 dark:text-slate-200 focus:outline-none"
                              />
                            </div>
                          </div>
                        </div>
                      ))}
                      <div className="flex items-center justify-between border-t border-slate-100 dark:border-slate-800 pt-3">
                        <button
                          type="button"
                          onClick={() => setEditableQuestions(prev => [...prev, { question: '', answer: '' }])}
                          className="text-xs font-bold text-[#405189] dark:text-indigo-400 hover:underline flex items-center gap-1 bg-transparent border-none cursor-pointer p-0"
                        >
                          <i className="fa-solid fa-plus text-[10px]"></i> Add Question
                        </button>
                        <div className="flex items-center gap-2">
                          <button
                            type="button"
                            onClick={() => setIsEditingQA(false)}
                            disabled={savingQA}
                            className="text-xs bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 px-3 py-1.5 font-bold transition cursor-pointer"
                          >
                            Cancel
                          </button>
                          <button
                            type="button"
                            onClick={saveQA}
                            disabled={savingQA}
                            className="text-xs bg-[#405189] hover:bg-[#334267] text-white px-3 py-1.5 font-bold transition cursor-pointer flex items-center gap-1.5"
                          >
                            {savingQA && <i className="fa-solid fa-spinner fa-spin text-[10px]" />}
                            {savingQA ? 'Saving...' : 'Save Q&A'}
                          </button>
                        </div>
                      </div>
                    </div>
                  ) : Array.isArray(job.questions) && job.questions.length > 0 ? (
                    <div className="space-y-2">
                      {job.questions.map((q, i) => {
                        const item = typeof q === 'string' ? { question: q, answer: '' } : q;
                        return (
                          <div key={i} className="text-xs">
                            <p className="font-semibold text-slate-700 dark:text-slate-200">Q{i + 1}. {item.question}</p>
                            {item.answer && <p className="text-slate-500 dark:text-slate-400 pl-5 mt-0.5">A. {item.answer}</p>}
                          </div>
                        );
                      })}
                    </div>
                  ) : (
                    <p className="text-xs text-slate-400 dark:text-slate-500 py-2">
                      No screening questions added yet.{' '}
                      {hasChangePermission && (
                        jdPublished ? (
                          <span
                            className="text-slate-300 dark:text-slate-600 font-bold cursor-not-allowed"
                            title="This JD is published and cannot be edited"
                          >
                            Add Questions & Answers now
                          </span>
                        ) : (
                          <button
                            type="button"
                            onClick={startEditingQA}
                            className="text-[#405189] dark:text-indigo-400 hover:underline font-bold bg-transparent border-none cursor-pointer p-0"
                          >
                            Add Questions & Answers now
                          </button>
                        )
                      )}
                    </p>
                  )}
                </div>
              </>)}
            </div>
          </>)}

          {/* ===== Tab: Job Post & Publish (Creative + Publish + Posting Status) ===== */}
          {tab === 'post' && (<>
            {/* Shareable job-post PNG */}
            <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-none p-6 shadow-sm">
              <button onClick={() => toggle('creative')} className="w-full flex items-center justify-between cursor-pointer">
                <h3 className="text-sm font-bold text-slate-800 dark:text-white">
                  <i className="fa-solid fa-image text-[#405189] mr-2"></i>Job Post Creative
                </h3>
                <i className={`fa-solid fa-chevron-down text-slate-400 text-xs transition-transform ${open.creative ? 'rotate-180' : ''}`}></i>
              </button>
              {open.creative && <div className="mt-4"><JobPostCard job={job} /></div>}
            </div>

            {/* Publish Job */}
            {canPost && (
              <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-none p-6 shadow-sm">
                <button onClick={() => toggle('publish')} className="w-full flex items-center justify-between cursor-pointer">
                  <h3 className="text-sm font-bold text-slate-800 dark:text-white">
                    <i className="fa-solid fa-paper-plane text-[#405189] mr-2"></i>Publish Job
                  </h3>
                  <i className={`fa-solid fa-chevron-down text-slate-400 text-xs transition-transform ${open.publish ? 'rotate-180' : ''}`}></i>
                </button>
                {open.publish && (<>
                  <p className="text-xs text-slate-400 mt-2 mb-4">
                    {jobActive
                      ? 'Select the job boards to publish this JD to.'
                      : `Only active (Published) JDs can be posted — this JD is ${job?.status}.`}
                  </p>

                  <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 mb-4">
                    {CHANNELS.map((ch) => {
                      const already = postedChannels.has(ch.key);
                      const checked = selected.includes(ch.key);
                      return (
                        <label
                          key={ch.key}
                          className={`flex items-center gap-3 border rounded-none px-4 py-3 transition ${already || !jobActive
                            ? 'opacity-60 cursor-not-allowed border-slate-200 dark:border-slate-800'
                            : checked
                              ? 'border-indigo-400 bg-indigo-50/60 dark:bg-indigo-950/30 dark:border-indigo-700 cursor-pointer'
                              : 'border-slate-200 dark:border-slate-800 hover:border-indigo-300 dark:hover:border-indigo-800 cursor-pointer'
                            }`}
                        >
                          <input
                            type="checkbox"
                            disabled={already || !jobActive}
                            checked={checked}
                            onChange={() => toggleChannel(ch.key)}
                            className="w-4 h-4 rounded-none accent-indigo-600 cursor-pointer disabled:cursor-not-allowed"
                          />
                          <i className={`${ch.icon} ${ch.color} text-lg`} />
                          <span className="min-w-0">
                            <span className="block text-xs font-bold text-slate-800 dark:text-slate-200">{ch.label}</span>
                            {already && <span className="block text-[10px] font-semibold text-emerald-600 dark:text-emerald-400">Already Posted</span>}
                          </span>
                        </label>
                      );
                    })}
                  </div>

                  <button
                    onClick={() => openPublishPreview(selected)}
                    disabled={posting || selected.length === 0 || !jobActive}
                    className="bg-indigo-600 hover:bg-indigo-500 disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm font-semibold px-5 py-2.5 rounded-none shadow-lg transition cursor-pointer flex items-center gap-2"
                  >
                    {posting && <i className="fa-solid fa-spinner fa-spin text-xs" />}
                    {posting ? 'Posting...' : `Post Selected ${selected.length > 0 ? `(${selected.length})` : ''}`}
                  </button>
                </>)}
              </div>
            )}

            {/* Posting Status */}
            {(canPost || postings.length > 0) && (
              <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-none p-6 shadow-sm">
                <button onClick={() => toggle('posting')} className="w-full flex items-center justify-between mb-4 cursor-pointer">
                  <h3 className="text-sm font-bold text-slate-800 dark:text-white">Posting Status <span className="text-slate-400 font-normal">({postings.filter((p) => p.status === 'POSTED').length}/{CHANNELS.length})</span></h3>
                  <i className={`fa-solid fa-chevron-down text-slate-400 text-xs transition-transform ${open.posting ? 'rotate-180' : ''}`}></i>
                </button>
                {open.posting && (
                  <div className="overflow-x-auto">
                    {canPost && !jobActive && (
                      <p className="mb-3 text-[11px] text-amber-600 dark:text-amber-400">
                        <i className="fa-solid fa-circle-info mr-1" />This JD must be <b>Published</b> before it can be posted to any channel (current status: {job.status}).
                      </p>
                    )}
                    <table className="w-full text-xs min-w-[560px]">
                      <thead>
                        <tr className="border-b border-slate-100 dark:border-slate-800">
                          {['Channel', 'Status', 'Posted Time', 'Details', 'Action'].map((h) => (
                            <th key={h} className="text-left py-2 px-2 text-[10px] font-extrabold uppercase tracking-wider text-slate-400">{h}</th>
                          ))}
                        </tr>
                      </thead>
                      <tbody className="divide-y divide-slate-50 dark:divide-slate-800">
                        {CHANNELS.map((chMeta) => {
                          const p = postings.find((x) => x.channel === chMeta.key);
                          const cfg = p ? STATUS_CFG[p.status] : null;
                          return (
                            <tr key={chMeta.key} className="hover:bg-slate-50 dark:hover:bg-slate-800/40 transition">
                              <td className="py-3 px-2">
                                <span className="flex items-center gap-2 font-bold text-slate-800 dark:text-slate-200">
                                  <i className={`${chMeta.icon} ${chMeta.color}`} />
                                  {chMeta.label}
                                </span>
                              </td>
                              <td className="py-3 px-2">
                                {p && cfg ? (
                                  <span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-bold border ${cfg.badge}`}>
                                    <span className={`w-1.5 h-1.5 rounded-full ${cfg.dot}`} />
                                    {cfg.label}
                                  </span>
                                ) : (
                                  <span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-bold border bg-slate-50 dark:bg-slate-800 text-slate-400 border-slate-200 dark:border-slate-700">
                                    <span className="w-1.5 h-1.5 rounded-full bg-slate-300" />
                                    Not posted
                                  </span>
                                )}
                              </td>
                              <td className="py-3 px-2 text-slate-500 dark:text-slate-400 whitespace-nowrap">{p ? friendlyTime(p.posted_at) : '--'}</td>
                              <td className="py-3 px-2 max-w-[220px]">
                                {p?.status === 'FAILED' ? (
                                  <span className="text-rose-500 dark:text-rose-455 text-[11px] leading-snug block truncate" title={p.error_message}>{p.error_message}</span>
                                ) : p?.posted_by_email ? (
                                  <span className="text-slate-400 text-[11px]">by {p.posted_by_email}</span>
                                ) : '--'}
                              </td>
                              <td className="py-3 px-2">
                                {p?.status === 'POSTED' && (
                                  <span className="inline-flex items-center gap-1.5">
                                    {p.external_url && (
                                      <a
                                        href={p.external_url}
                                        target="_blank"
                                        rel="noreferrer"
                                        className="inline-flex items-center gap-1.5 text-[11px] font-bold text-indigo-600 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-900/50 rounded-none px-2.5 py-1 hover:bg-indigo-600 hover:text-white transition"
                                      >
                                        <i className="fa-solid fa-arrow-up-right-from-square text-[9px]" /> Open
                                      </a>
                                    )}
                                  </span>
                                )}
                                {p?.status === 'FAILED' && canPost && (
                                  <button
                                    onClick={() => openPublishPreview([chMeta.key], true)}
                                    disabled={!jobActive}
                                    title={!jobActive ? `JD must be Published (currently ${job.status})` : `Retry posting to ${chMeta.label}`}
                                    className="inline-flex items-center gap-1.5 text-[11px] font-bold text-rose-600 dark:text-rose-455 border border-rose-200 dark:border-rose-900/50 rounded-none px-2.5 py-1 hover:bg-rose-600 hover:text-white transition disabled:opacity-40 disabled:cursor-not-allowed"
                                  >
                                    <i className="fa-solid fa-rotate-right text-[9px]" /> Retry
                                  </button>
                                )}
                                {p?.status === 'PENDING' && (
                                  <i className="fa-solid fa-spinner fa-spin text-amber-500 text-sm" />
                                )}
                                {!p && canPost && (
                                  <button
                                    onClick={() => openPublishPreview([chMeta.key])}
                                    disabled={!jobActive}
                                    title={!jobActive ? `JD must be Published (currently ${job.status})` : `Post this JD to ${chMeta.label}`}
                                    className="inline-flex items-center gap-1.5 text-[11px] font-bold text-[#405189] dark:text-indigo-400 border border-[#405189]/30 dark:border-indigo-900/50 rounded-none px-2.5 py-1 hover:bg-[#405189] hover:text-white transition disabled:opacity-40 disabled:cursor-not-allowed"
                                  >
                                    <i className="fa-solid fa-paper-plane text-[9px]" /> Post
                                  </button>
                                )}
                              </td>
                            </tr>
                          );
                        })}
                      </tbody>
                    </table>
                  </div>
                )}
              </div>
            )}
          </>)}

          {/* ===== Tab: Candidate Pipeline (how many candidates added to this JD) ===== */}
          {tab === 'pipeline' && (
            <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-none p-6 shadow-sm">
              <h3 className="text-sm font-bold text-slate-800 dark:text-white mb-4">
                <i className="fa-solid fa-diagram-project text-[#405189] mr-2"></i>Candidate Pipeline
              </h3>
              {!jobActive && (
                <p className="text-[11px] text-slate-400 mt-3">This JD is not Published yet, so it has no candidate pipeline.</p>
              )}

              {jobActive && (
                <div className="mt-5">
                  <DataTable
                    columns={pipelineColumns}
                    data={pipelineApps || []}
                    loading={pipelineAppsLoading}
                    searchPlaceholder="Search candidates by name, location, stage…"
                    filters={pipelineFilters}
                    exportConfig={{ fileName: 'candidate_pipeline' }}
                    emptyStateTitle="No candidates"
                    emptyStateDescription="No candidates have been added to this JD yet."
                  />
                </div>
              )}
            </div>
          )}

          {/* ===== Tab: AI Candidate Screening (ranking of candidates added to this JD) ===== */}
          {tab === 'screening' && (
            <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-none p-6 shadow-sm">
              <div className="flex items-center justify-between mb-4 gap-3 flex-wrap">
                <h3 className="text-sm font-bold text-slate-800 dark:text-white">
                  <i className="fa-solid fa-wand-magic-sparkles text-[#0ab39c] mr-2"></i>AI Candidate Screening
                </h3>
                {rankRun && (
                  <span className="text-[10px] text-slate-400">
                    Last run: {formatDateTime(rankRun.created_at)} · {rankRun.provider} · {rankRun.candidate_count} candidate(s)
                  </span>
                )}
              </div>

              {/* Sub-tabs inside AI Candidate Screening */}
              <div className="flex border-b border-slate-100 dark:border-slate-800 mb-4">
                {([
                  ['ranking', 'Candidate Ranking', 'fa-ranking-star'],
                  ['notifications', 'Send History', 'fa-bell'],
                  // Additive third tab — the existing two are untouched.
                  ...(canCall ? [['calls', 'Call History', 'fa-phone-volume']] : []),
                ] as ['ranking' | 'notifications' | 'calls', string, string][]).map(([key, label, icon]) => (
                  <button
                    key={key}
                    type="button"
                    onClick={() => setScreeningTab(key)}
                    className={`flex items-center gap-1.5 px-3 py-2 text-xs font-bold border-b-2 -mb-px transition cursor-pointer ${
                      screeningTab === key
                        ? 'border-[#405189] text-[#405189] dark:border-indigo-400 dark:text-indigo-400'
                        : 'border-transparent text-slate-400 hover:text-slate-600 dark:hover:text-slate-300'
                    }`}
                  >
                    <i className={`fa-solid ${icon} text-[11px]`} />{label}
                    {key === 'notifications' && notifLogs.length > 0 && (
                      <span className="ml-1 inline-flex items-center justify-center min-w-[16px] h-4 px-1 rounded-full bg-slate-200 dark:bg-slate-700 text-[9px] font-black text-slate-600 dark:text-slate-300">
                        {notifLogs.length}
                      </span>
                    )}
                    {key === 'calls' && callHistory.length > 0 && (
                      <span className="ml-1 inline-flex items-center justify-center min-w-[16px] h-4 px-1 rounded-full bg-slate-200 dark:bg-slate-700 text-[9px] font-black text-slate-600 dark:text-slate-300">
                        {callHistory.length}
                      </span>
                    )}
                  </button>
                ))}
              </div>

              {screeningTab === 'notifications' ? (
                <>
                  <div className="flex items-center justify-between gap-3 mb-2">
                    <p className="text-[11px] text-slate-400">
                      Notifications sent to this job description&apos;s screened candidates.
                    </p>
                    <button
                      onClick={() => loadNotifLogs(rankedCandidateIds)}
                      disabled={notifLoading}
                      className="text-[11px] font-bold border border-slate-200 dark:border-slate-700 px-2.5 py-1.5 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800 transition cursor-pointer disabled:opacity-50"
                    >
                      <i className={`fa-solid ${notifLoading ? 'fa-spinner fa-spin' : 'fa-rotate'} mr-1.5`} />Refresh
                    </button>
                  </div>
                  <DataTable
                    columns={notifColumns}
                    data={notifRows}
                    loading={notifLoading}
                    compact
                    collapsibleFilters
                    filters={notifFilters}
                    exportConfig={{ fileName: 'send_history' }}
                    searchPlaceholder="Search by candidate, email, mobile…"
                    emptyStateTitle="No notifications yet"
                    emptyStateDescription="Emails, SMS and WhatsApp notifications sent from this screening page will appear here."
                  />
                </>
              ) : screeningTab === 'calls' ? (
                <>
                  <div className="flex items-center justify-between gap-3 mb-2">
                    <p className="text-[11px] text-slate-400">
                      AI screening calls placed for this job description. Statuses are exactly what Hunar reports.
                    </p>
                    <button
                      onClick={loadCallHistory}
                      disabled={callHistoryLoading}
                      className="text-[11px] font-bold border border-slate-200 dark:border-slate-700 px-2.5 py-1.5 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800 transition cursor-pointer disabled:opacity-50"
                    >
                      <i className={`fa-solid ${callHistoryLoading ? 'fa-spinner fa-spin' : 'fa-rotate'} mr-1.5`} />Refresh
                    </button>
                  </div>
                  <DataTable
                    columns={callHistoryColumns}
                    data={callHistory}
                    loading={callHistoryLoading}
                    compact
                    collapsibleFilters
                    filters={callHistoryFilters}
                    exportConfig={{ fileName: 'call_history' }}
                    searchPlaceholder="Search by candidate, mobile, recruiter…"
                    emptyStateTitle="No AI calls yet"
                    emptyStateDescription="Select candidates in Candidate Ranking and use Call Candidates to place AI screening calls."
                  />
                </>
              ) : (<>
                {/* Shortlist URL & Share Tracking Links */}
                <div className="mb-4 p-3 border border-[#405189]/20 bg-[#405189]/5 text-xs flex justify-between items-center gap-4 flex-wrap">
                  <div className="flex-1 min-w-0">
                    {/* Single line: the description now follows the label inline
                        instead of wrapping onto its own paragraph. */}
                    <span className="font-bold text-[#405189]">Pipeline Actions &amp; Source Tracking Links</span>{' '}
                    <span className="text-slate-400 font-normal">•</span>{' '}
                    <span className="text-[10px] text-slate-500 font-normal">Copy tracking links and calculate candidate rankings.</span>
                  </div>
                  <div className="flex items-center gap-1.5 flex-wrap">
                    {/* The per-platform "copy tracking link" buttons (LinkedIn /
                        WhatsApp / SMS / Telegram / Direct) were removed from this
                        toolbar on request. Platform link generation itself is
                        unchanged and lives on the backend — the providers in
                        apps/jobs/services/integrations build every application URL
                        through build_application_url(), which mints the encrypted
                        `?t=` tracking token (see apps/jobs/tracking_tokens.py) and
                        is surfaced by GET /jobs/<id>/publish-preview/. */}
                    <button
                      onClick={shareShortlist}
                      className="bg-slate-700 hover:bg-slate-600 text-white font-bold px-2.5 py-1 text-[11px] transition cursor-pointer flex items-center gap-1 ml-1"
                    >
                      <i className="fa-solid fa-share-nodes text-[10px]"></i> Shortlist
                    </button>
                    {canRank && (
                      <>
                        <button
                          onClick={() => setShowWeights(!showWeights)}
                          className="bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 font-bold px-3 py-1.5 text-xs transition cursor-pointer"
                        >
                          <i className="fa-solid fa-sliders mr-1"></i> Weights
                        </button>
                        <button
                          onClick={runRanking}
                          disabled={ranking}
                          className="bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white font-bold px-3 py-1.5 text-xs transition cursor-pointer flex items-center gap-1.5"
                        >
                          {ranking && <i className="fa-solid fa-spinner fa-spin text-[10px]" />}
                          <i className="fa-solid fa-play text-[10px]"></i> Rank Candidates
                        </button>
                      </>
                    )}
                    {/* Bulk communication — sends to the selected candidates only,
                        via the existing notification service. Disabled until at
                        least one candidate is checked. */}
                    {canNotify && (
                      <div className="flex items-center gap-1.5">
                        <select
                          value={notifType}
                          onChange={(e) => setNotifType(e.target.value)}
                          disabled={bulkSending !== null}
                          className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 text-slate-800 dark:text-slate-200 font-semibold text-xs px-2.5 py-1.5 rounded-none focus:outline-none focus:border-indigo-500 transition disabled:opacity-50 cursor-pointer"
                        >
                          <option value="Send Email">Send Email</option>
                          <option value="Send SMS">Send SMS</option>
                          <option value="Send WhatsApp">Send WhatsApp</option>
                          <option value="Send WhatsApp, SMS & Email">Send WhatsApp, SMS & Email</option>
                        </select>
                        <button
                          onClick={sendSelectedNotifications}
                          disabled={selectedEmailIds.length === 0 || bulkSending !== null}
                          title={selectedEmailIds.length === 0 ? 'Select one or more candidates first' : `${notifType} to the selected candidates`}
                          className="bg-indigo-600 hover:bg-indigo-500 disabled:opacity-40 disabled:cursor-not-allowed text-white font-bold px-3.5 py-1.5 text-xs transition cursor-pointer flex items-center gap-1.5"
                        >
                          {bulkSending !== null ? (
                            <i className="fa-solid fa-spinner fa-spin text-[10px]" />
                          ) : (
                            <i className="fa-solid fa-paper-plane text-[10px]" />
                          )}
                          {bulkSending !== null ? 'Sending...' : `Send${selectedEmailIds.length > 0 ? ` (${selectedEmailIds.length})` : ''}`}
                        </button>
                      </div>
                    )}

                    {/* Bulk AI calling — beside Send, nothing replaced. Calls the
                        existing /ai-calls/start/ once per selected candidate so
                        they go out one at a time with real progress. */}
                    {canCall && (
                      <button
                        onClick={callSelectedCandidates}
                        disabled={selectedEmailIds.length === 0 || bulkCalling}
                        title={selectedEmailIds.length === 0
                          ? 'Select one or more candidates first'
                          : 'Place AI screening calls to the selected candidates, one at a time'}
                        className="bg-[#0ab39c] hover:bg-[#099885] disabled:opacity-40 disabled:cursor-not-allowed text-white font-bold px-3.5 py-1.5 text-xs transition cursor-pointer flex items-center gap-1.5"
                      >
                        <i className={`fa-solid ${bulkCalling ? 'fa-spinner fa-spin' : 'fa-phone-volume'} text-[10px]`} />
                        {bulkCalling ? 'Calling...' : `Call Candidates${selectedEmailIds.length > 0 ? ` (${selectedEmailIds.length})` : ''}`}
                      </button>
                    )}
                  </div>
                </div>

                {/* Live bulk-calling progress — "Calling N of M", per-candidate
                    outcome tallies and the candidate currently being dialled. */}
                {callProgress && (() => {
                  const p = callProgress;
                  const pct = p.total ? Math.round((p.processed / p.total) * 100) : 0;
                  return (
                    <div className="mb-4 p-3 border border-[#0ab39c]/30 bg-[#0ab39c]/5">
                      <div className="flex items-center justify-between gap-3 flex-wrap">
                        <p className="text-xs font-bold text-[#0ab39c]">
                          {p.done
                            ? `Finished — ${p.total} candidate${p.total === 1 ? '' : 's'} processed`
                            : `Calling ${Math.min(p.processed + 1, p.total)} of ${p.total}…`}
                          {!p.done && p.currentName ? (
                            <span className="font-semibold text-slate-600 dark:text-slate-300"> {p.currentName}</span>
                          ) : null}
                        </p>
                        <button
                          onClick={() => setCallProgress(null)}
                          disabled={bulkCalling}
                          className="text-[10px] font-bold text-slate-400 hover:text-slate-600 disabled:opacity-40 cursor-pointer"
                        >
                          Dismiss
                        </button>
                      </div>
                      <div className="mt-2 h-1.5 bg-slate-200 dark:bg-slate-700 overflow-hidden">
                        <div className="h-full bg-[#0ab39c] transition-all duration-300" style={{ width: `${pct}%` }} />
                      </div>
                      <div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-[10px] font-bold text-slate-500 dark:text-slate-400">
                        <span>Total Selected: <b className="text-slate-700 dark:text-slate-200">{p.total}</b></span>
                        <span>Completed: <b className="text-emerald-600">{p.completed}</b></span>
                        <span>Failed: <b className="text-rose-600">{p.failed}</b></span>
                        <span>Progress: <b className="text-slate-700 dark:text-slate-200">{pct}%</b></span>
                      </div>
                    </div>
                  );
                })()}

                {/* Live sending progress — visible ONLY in Candidate Ranking tab near the notification action buttons. */}
                {bulkProgress && (() => {
                  const p = bulkProgress;
                  const pct = p.total ? Math.round((p.processed / p.total) * 100) : 0;
                  const running = bulkSending !== null;
                  return (
                    <div className="mb-4 border border-[#405189]/25 bg-[#405189]/5 p-4">
                      <div className="flex flex-wrap items-center justify-between gap-2 mb-1.5">
                        <p className="text-xs font-extrabold text-[#405189]">
                          {running
                            ? <><i className="fa-solid fa-spinner fa-spin mr-1.5" />Sending Notifications… ({p.action})</>
                            : <><i className="fa-solid fa-circle-check mr-1.5" />Completed ({p.action})</>}
                        </p>
                        <span className="text-xs font-extrabold text-[#405189]">{pct}%</span>
                      </div>
                      <p className="text-[10px] uppercase font-extrabold tracking-wider text-slate-400 mb-1">Progress</p>
                      <div className="h-2.5 w-full bg-slate-200 dark:bg-slate-800 overflow-hidden">
                        <div className="h-full bg-[#405189] transition-all duration-300" style={{ width: `${pct}%` }} />
                      </div>
                      <p className="text-xs font-bold text-slate-600 dark:text-slate-300 mt-2">
                        {running ? (
                          `Sending ${Math.min(p.processed + 1, p.total)} of ${p.total}...`
                        ) : (
                          `${p.processed} / ${p.total} candidate${p.total === 1 ? '' : 's'} processed`
                        )}
                        <span className="font-normal text-slate-400"> · {p.sent} notification{p.sent === 1 ? '' : 's'} sent</span>
                      </p>
                      <div className="flex flex-wrap gap-x-5 gap-y-1 mt-1.5 text-[11px] font-bold">
                        <span className="text-emerald-600 dark:text-emerald-400">Success: {p.sent}</span>
                        <span className="text-rose-600 dark:text-rose-400">Failed: {p.failed}</span>
                        <span className="text-amber-600 dark:text-amber-400">Remaining: {Math.max(0, p.total - p.processed)} candidate{p.total - p.processed === 1 ? '' : 's'}</span>
                      </div>
                      {/* Per-channel breakdown — how many emails / SMS / WhatsApp went out */}
                      {Object.keys(p.perChannel).length > 0 && (
                        <div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 pt-2 border-t border-[#405189]/15 text-[11px] font-bold">
                          {(['EMAIL', 'SMS', 'WHATSAPP'] as const).map((ch) => (
                            p.perChannel[ch] ? (
                              <span key={ch} className="text-slate-600 dark:text-slate-300">
                                <i className={`mr-1 ${ch === 'WHATSAPP' ? 'fa-brands fa-whatsapp text-[#0ab39c]' : ch === 'SMS' ? 'fa-solid fa-comment-sms text-[#299cdb]' : 'fa-solid fa-envelope text-[#405189]'}`} />
                                {ch === 'WHATSAPP' ? 'WhatsApp' : ch === 'SMS' ? 'SMS' : 'Email'}: {p.perChannel[ch]} sent
                              </span>
                            ) : null
                          ))}
                        </div>
                      )}
                    </div>
                  );
                })()}

                {showWeights && (
                  <div className="mb-4 p-3 border border-slate-200 dark:border-slate-800 bg-slate-50 dark:bg-slate-950/40">
                    <div className="flex items-center justify-between mb-2">
                      <span className="text-[11px] font-extrabold text-slate-650 dark:text-slate-300 uppercase tracking-wider">Scoring Weights (must total 100)</span>
                      {(() => {
                        const t = Object.values(weights).reduce((a, b) => a + Number(b || 0), 0);
                        return <span className={`text-[11px] font-bold ${t === 100 ? 'text-emerald-600' : 'text-rose-500'}`}>Total: {t}</span>;
                      })()}
                    </div>
                    <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
                      {rankParams.map((p) => (
                        <div key={p.key}>
                          <label className="block text-[10px] uppercase font-bold text-slate-400 mb-1">{p.label}</label>
                          <input type="number" min="0" max="100" value={weights[p.key] ?? 0}
                            onChange={(e) => setWeights({ ...weights, [p.key]: Number(e.target.value) })}
                            className="w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 px-2 py-1.5 text-xs focus:outline-none text-slate-850 dark:text-white" />
                        </div>
                      ))}
                    </div>
                  </div>
                )}

                <DataTable
                  columns={rankColumns}
                  data={rankScores}
                  searchPlaceholder="Search ranked candidates…"
                  emptyStateTitle="No screening yet"
                  emptyStateDescription="Click Rank Candidates to screen the candidates added to this JD."
                  enableRowSelection={canNotify}
                  rowSelection={emailSel}
                  onRowSelectionChange={setEmailSel}
                  getRowId={(r: any) => String(r.candidate_id)}
                />
              </>)}
            </div>
          )}

          {/* ===== Tab: Change History ===== */}
          {tab === 'history' && (
            <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-none p-6 shadow-sm">
              <h3 className="text-sm font-bold text-slate-800 dark:text-white mb-4">
                <i className="fa-solid fa-clock-rotate-left text-[#405189] mr-2"></i>Change History
              </h3>
              {(audit.length === 0 ? (
                <p className="text-xs text-slate-400 text-center py-8">No changes recorded yet.</p>
              ) : (
                <div className="overflow-x-auto">
                  <table className="w-full text-xs min-w-[560px]">
                    <thead>
                      <tr className="border-b border-slate-100 dark:border-slate-800">
                        {['#', 'Action', 'Changes', 'By', 'Date & Time'].map((h) => (
                          <th key={h} className="text-left py-2 px-2 text-[10px] font-extrabold uppercase tracking-wider text-slate-400">{h}</th>
                        ))}
                      </tr>
                    </thead>
                    <tbody className="divide-y divide-slate-50 dark:divide-slate-800">
                      {audit.map((a, i) => (
                        <tr key={a.id} className="hover:bg-slate-50 dark:hover:bg-slate-800/40 transition align-top">
                          <td className="py-3 px-2 text-slate-400 font-semibold">{i + 1}</td>
                          <td className="py-3 px-2">
                            <span className="inline-flex px-2 py-0.5 text-[10px] font-bold border border-[#405189]/20 bg-[#405189]/10 text-[#405189] dark:text-indigo-300 whitespace-nowrap">
                              {a.action.replace(/_/g, ' ')}
                            </span>
                          </td>
                          <td className="py-3 px-2 text-slate-600 dark:text-slate-300">{a.changes || '—'}</td>
                          <td className="py-3 px-2 font-semibold text-slate-700 dark:text-slate-350 whitespace-nowrap">{a.by || '—'}</td>
                          <td className="py-3 px-2 text-slate-400 whitespace-nowrap">{formatDateTime(a.created_at)}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              ))}
            </div>
          )}
        </div>
      </main>

      {/* Rationale viewer popup (AI Candidate Screening) */}
      {rationaleView && (
        <div className="fixed inset-0 z-[150] flex items-center justify-center p-4" onClick={() => setRationaleView(null)}>
          <div className="fixed inset-0 bg-slate-950/40 dark:bg-slate-950/80 backdrop-blur-sm"></div>
          <div className="relative z-10 bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 shadow-2xl w-full max-w-md" onClick={(e) => e.stopPropagation()}>
            <div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 dark:border-slate-800">
              <h3 className="text-sm font-bold text-slate-800 dark:text-white">
                <i className="fa-solid fa-wand-magic-sparkles text-[#0ab39c] mr-2" />Screening rationale · {rationaleView.name}
              </h3>
              <button onClick={() => setRationaleView(null)} className="text-slate-400 hover:text-slate-600 dark:hover:text-white cursor-pointer" title="Close">
                <i className="fa-solid fa-xmark" />
              </button>
            </div>
            <div className="p-5 text-sm text-slate-700 dark:text-slate-300 whitespace-pre-line max-h-[60vh] overflow-y-auto">
              {rationaleView.text}
            </div>
          </div>
        </div>
      )}

      {/* Publish popup */}
      {publishStep && (
        <div className="fixed inset-0 z-[150] flex items-center justify-center p-4">
          <div className="fixed inset-0 bg-slate-950/40 dark:bg-slate-950/80 backdrop-blur-sm" onClick={() => !posting && closePublishModal()}></div>
          <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 w-full max-w-2xl rounded-none relative z-10 shadow-2xl animate-in fade-in zoom-in duration-200 text-slate-800 dark:text-slate-100 flex flex-col max-h-[90vh]">
            <div className="flex items-center justify-between px-6 pt-5 pb-4 border-b border-slate-100 dark:border-slate-800 shrink-0">
              <div className="flex items-center gap-3">
                <div className="w-10 h-10 rounded-none bg-indigo-50 dark:bg-indigo-950/40 text-indigo-600 dark:text-indigo-400 flex items-center justify-center">
                  <i className={`fa-solid ${publishStep === 'preview' ? 'fa-paper-plane' : publishStep === 'confirm' ? 'fa-circle-question' : 'fa-circle-check'}`}></i>
                </div>
                <div>
                  <h3 className="text-base font-bold text-slate-900 dark:text-white leading-tight">
                    {publishStep === 'preview' ? 'Job Posting' : publishStep === 'confirm' ? 'Confirm Publish' : 'Publish Results'}
                  </h3>
                  <p className="text-xs text-slate-400 mt-0.5">
                    {publishStep === 'preview' ? 'Preview details and select platforms to publish.' : 'Per-channel publish run status.'}
                  </p>
                </div>
                {/* Publish status of this JD — Draft until it has actually gone
                    out to a channel, Published afterwards. */}
                {(() => {
                  const isPublished = postedChannels.size > 0;
                  return (
                    <span
                      title={isPublished
                        ? 'This JD has already been published to at least one platform.'
                        : 'This JD has not been published to any platform yet.'}
                      className={`inline-flex items-center gap-1.5 text-[10px] font-extrabold uppercase tracking-wider border px-2.5 py-1 rounded-none ${isPublished
                        ? 'bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-400 border-emerald-200 dark:border-emerald-800'
                        : 'bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-400 border-amber-200 dark:border-amber-800'}`}
                    >
                      <i className={`fa-solid ${isPublished ? 'fa-circle-check' : 'fa-pen-ruler'} text-[9px]`} />
                      {isPublished ? 'Published' : 'Draft'}
                    </span>
                  );
                })()}
              </div>
              <button onClick={closePublishModal} disabled={posting}
                className="w-8 h-8 rounded-none flex items-center justify-center text-slate-400 hover:text-slate-700 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer disabled:opacity-50">
                <i className="fa-solid fa-xmark"></i>
              </button>
            </div>

            <div className="px-6 py-4 overflow-y-auto custom-scrollbar flex-1 min-h-0">
              {publishStep === 'results' && (
                <div className="flex flex-wrap items-center gap-2 mb-4">
                  <span className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400">Channels:</span>
                  {publishChannels.map((key) => {
                    const meta = CHANNELS.find((c) => c.key === key);
                    return (
                      <span key={key} className="inline-flex items-center gap-1.5 text-xs font-bold bg-slate-50 dark:bg-slate-800/60 border border-slate-200 dark:border-slate-700 rounded-full px-2.5 py-1">
                        <i className={`${meta?.icon ?? 'fa-solid fa-globe'} ${meta?.color ?? ''}`} />
                        {meta?.label ?? key}
                      </span>
                    );
                  })}
                </div>
              )}

              {publishStep === 'confirm' && (
                <div className="py-2">
                  <p className="text-sm text-slate-755 dark:text-slate-200 font-bold">You are about to publish this Job Description to:</p>
                  <ul className="mt-3 space-y-2">
                    {publishChannels.map((key) => {
                      const meta = CHANNELS.find((c) => c.key === key);
                      return (
                        <li key={key} className="flex items-center gap-2.5 text-sm font-bold text-slate-850 dark:text-slate-100">
                          <span className="text-slate-400">•</span>
                          <i className={`${meta?.icon ?? 'fa-solid fa-globe'} ${meta?.color ?? ''}`} />
                          {meta?.label ?? key}
                        </li>
                      );
                    })}
                  </ul>
                  <p className="mt-4 text-sm font-semibold text-slate-700 dark:text-slate-200">Continue?</p>
                </div>
              )}

              {publishStep === 'preview' && (
                <>
                  <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mb-2">Job Preview</p>
                  <div className="rounded-none overflow-hidden border border-slate-200 dark:border-slate-800">
                    <div className="p-6 text-white" style={{ background: 'linear-gradient(135deg,#405189 0%,#3b4a7e 55%,#28345c 100%)' }}>
                      <p className="text-xs font-extrabold tracking-widest text-[#0ab39c] uppercase">{job.client_name || 'TA-ATS'}</p>
                      <p className="text-2xl font-black mt-1">WE&apos;RE HIRING</p>
                      <div className="w-16 h-1 bg-[#0ab39c] rounded-none mt-1.5 mb-3" />
                      <p className="text-lg font-bold">{job.title}</p>
                      <div className="grid grid-cols-2 gap-x-6 gap-y-2 mt-4 text-sm">
                        {[
                          ['📍 Location', job.location || '—'],
                          ['💼 Experience', job.experience_band || '—'],
                          ['💰 CTC', job.ctc_band || '—'],
                          ['🏢 Department', job.department || '—'],
                        ].map(([label, value]) => (
                          <p key={label}><span className="opacity-60">{label}:</span> <b>{value}</b></p>
                        ))}
                      </div>
                    </div>
                  </div>

                  <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mt-5 mb-2">Select Platforms</p>
                  <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
                    {CHANNELS.map((ch) => {
                      const already = postedChannels.has(ch.key) && !forceRepost;
                      const checked = publishChannels.includes(ch.key);
                      return (
                        <label
                          key={ch.key}
                          className={`flex items-center gap-3 border rounded-none px-4 py-3 transition ${already
                            ? 'opacity-60 cursor-not-allowed border-slate-200 dark:border-slate-800'
                            : checked
                              ? 'border-indigo-400 bg-indigo-50/60 dark:bg-indigo-950/30 dark:border-indigo-700 cursor-pointer'
                              : 'border-slate-200 dark:border-slate-800 hover:border-indigo-300 dark:hover:border-indigo-800 cursor-pointer'
                            }`}
                        >
                          <input
                            type="checkbox"
                            disabled={already || posting}
                            checked={checked}
                            onChange={() => togglePublishChannel(ch.key)}
                            className="w-4 h-4 rounded-none accent-indigo-600 cursor-pointer disabled:cursor-not-allowed"
                          />
                          <i className={`${ch.icon} ${ch.color} text-lg`} />
                          <span className="min-w-0">
                            <span className="block text-xs font-bold text-slate-800 dark:text-slate-200">{ch.label}</span>
                          </span>
                        </label>
                      );
                    })}
                  </div>

                  <div className="flex items-center justify-between mt-5 mb-2">
                    <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400">Preview Links</p>
                    <span className="inline-flex items-center gap-1 text-[10px] font-bold text-slate-400">
                      <i className="fa-solid fa-lock text-[9px]" /> Secure source tracking
                    </span>
                  </div>
                  {previewLoading ? (
                    <p className="text-xs text-slate-400 flex items-center gap-2 px-1 py-2">
                      <i className="fa-solid fa-spinner fa-spin" /> Generating application links…
                    </p>
                  ) : !previewLinks ? (
                    <p className="text-xs text-slate-400 px-1 py-2">Preview links are unavailable right now.</p>
                  ) : publishChannels.length === 0 ? (
                    <p className="text-xs text-slate-400 px-1 py-2">Select a platform above to preview its application link.</p>
                  ) : (
                    <div className="space-y-2">
                      {previewLinks
                        .filter((l) => publishChannels.includes(l.channel))
                        .map((l) => {
                          const meta = CHANNELS.find((c) => c.key === l.channel);
                          return (
                            <div key={l.channel} className="border border-slate-200 dark:border-slate-800 rounded-none px-3 py-2.5">
                              <div className="flex items-center gap-2 mb-1.5">
                                <i className={`${meta?.icon ?? 'fa-solid fa-globe'} ${meta?.color ?? ''} text-sm`} />
                                <span className="text-[11px] font-bold text-slate-700 dark:text-slate-200">{l.channel_label}</span>
                              </div>
                              <div className="flex items-center gap-2">
                                <input
                                  readOnly
                                  value={l.url}
                                  onFocus={(e) => e.currentTarget.select()}
                                  className="flex-1 min-w-0 px-2 py-1.5 text-[11px] font-mono bg-slate-50 dark:bg-slate-950/60 border border-slate-200 dark:border-slate-800 text-slate-600 dark:text-slate-300 focus:outline-none focus:border-indigo-400 cursor-text"
                                />
                                <button
                                  type="button"
                                  onClick={() => copyPreviewLink(l)}
                                  title="Copy link"
                                  className="shrink-0 h-8 px-2.5 border border-slate-200 dark:border-slate-800 text-slate-500 hover:text-indigo-600 hover:border-indigo-300 dark:hover:text-indigo-400 transition cursor-pointer"
                                >
                                  <i className={`fa-solid ${copiedLink === l.channel ? 'fa-check text-emerald-600' : 'fa-copy'} text-xs`} />
                                </button>
                                <a
                                  href={l.url} target="_blank" rel="noreferrer"
                                  title="Open link"
                                  className="shrink-0 h-8 px-2.5 flex items-center border border-slate-200 dark:border-slate-800 text-slate-500 hover:text-indigo-600 hover:border-indigo-300 dark:hover:text-indigo-400 transition"
                                >
                                  <i className="fa-solid fa-arrow-up-right-from-square text-xs" />
                                </a>
                              </div>
                            </div>
                          );
                        })}
                    </div>
                  )}
                </>
              )}

              {publishStep === 'results' && (
                <div className="space-y-2">
                  {publishResults.map((r) => {
                    const meta = CHANNELS.find((c) => c.key === r.channel);
                    const posted = r.status === 'POSTED';
                    const already = r.status === 'ALREADY_POSTED';
                    return (
                      <div key={r.channel}
                        className={`flex items-start gap-3 border rounded-none px-4 py-3 ${posted ? 'border-emerald-200 dark:border-emerald-900/40 bg-emerald-50/50 dark:bg-emerald-950/20'
                          : already ? 'border-blue-200 dark:border-blue-900/40 bg-blue-50/50 dark:bg-blue-950/20'
                            : 'border-rose-200 dark:border-rose-900/40 bg-rose-50/50 dark:bg-rose-950/20'
                          }`}>
                        <i className={`${meta?.icon ?? 'fa-solid fa-globe'} ${meta?.color ?? ''} text-lg mt-0.5`} />
                        <div className="min-w-0 flex-1">
                          <div className="flex items-center gap-2 flex-wrap">
                            <span className="text-sm font-bold text-slate-800 dark:text-slate-200">{meta?.label ?? r.channel}</span>
                            <span className={`text-[10px] font-extrabold px-2 py-0.5 rounded-full ${posted ? 'bg-emerald-600 text-white' : already ? 'bg-blue-600 text-white' : 'bg-rose-600 text-white'
                              }`}>
                              {posted ? 'POST PUBLISHED ✓' : already ? 'ALREADY POSTED' : 'FAILED'}
                            </span>
                          </div>
                          {r.error_message && <p className="text-xs text-rose-600 dark:text-rose-400 mt-1">{r.error_message}</p>}
                        </div>
                      </div>
                    );
                  })}
                </div>
              )}
            </div>

            <div className="flex justify-end gap-3 px-6 py-4 border-t border-slate-100 dark:border-slate-800 shrink-0">
              {publishStep === 'preview' ? (
                <>
                  <button onClick={closePublishModal} disabled={posting}
                    className="px-4 py-2 text-sm text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-white transition cursor-pointer disabled:opacity-50">
                    Cancel
                  </button>
                  <button
                    onClick={() => { if (publishChannels.length > 0) setPublishStep('confirm'); }}
                    disabled={publishChannels.length === 0}
                    className="bg-indigo-600 hover:bg-indigo-500 disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm font-semibold px-5 py-2.5 rounded-none shadow-lg transition cursor-pointer flex items-center gap-2">
                    <i className="fa-solid fa-paper-plane text-xs" />
                    Post Now{publishChannels.length > 0 ? ` (${publishChannels.length})` : ''}
                  </button>
                </>
              ) : publishStep === 'confirm' ? (
                <>
                  <button onClick={() => setPublishStep('preview')} disabled={posting}
                    className="px-4 py-2 text-sm text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-white transition cursor-pointer disabled:opacity-50">
                    Back
                  </button>
                  <button onClick={confirmPublish} disabled={posting}
                    className="bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white text-sm font-semibold px-5 py-2.5 rounded-none shadow-lg transition cursor-pointer flex items-center gap-2">
                    {posting && <i className="fa-solid fa-spinner fa-spin text-xs" />}
                    {posting ? 'Publishing...' : 'Continue'}
                  </button>
                </>
              ) : (
                <button onClick={closePublishModal}
                  className="bg-[#405189] hover:bg-[#364574] text-white text-sm font-semibold px-5 py-2.5 rounded-none shadow-lg transition cursor-pointer">
                  Done
                </button>
              )}
            </div>
          </div>
        </div>
      )}

      {/* ===== Call Summary popup (Call History tab) =====
          Shows the summary Hunar already produced — fetched from the existing
          /ai-calls/<id>/transcript/ endpoint and never regenerated here. The API
          withholds every artefact unless the call actually completed. */}
      {callSummaryRow && (
        <div className="fixed inset-0 z-[200] flex items-center justify-center p-4">
          <div className="fixed inset-0 bg-slate-950/40 dark:bg-slate-950/80 backdrop-blur-sm" onClick={() => setCallSummaryRow(null)}></div>
          <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 w-full max-w-2xl rounded-none relative z-10 shadow-2xl text-slate-800 dark:text-slate-100 flex flex-col max-h-[90vh]">
            <div className="flex items-center justify-between px-6 pt-5 pb-4 border-b border-slate-100 dark:border-slate-800 shrink-0">
              <div className="flex items-center gap-3">
                <div className="w-10 h-10 rounded-none bg-[#0ab39c]/10 text-[#0ab39c] flex items-center justify-center">
                  <i className="fa-solid fa-headset"></i>
                </div>
                <div>
                  <h3 className="text-base font-bold text-slate-900 dark:text-white leading-tight">AI Call Summary</h3>
                  <p className="text-xs text-slate-400 mt-0.5">
                    {callSummaryRow.candidate_name}
                    {callSummaryRow.job_title ? ` · ${callSummaryRow.job_title}` : ''}
                  </p>
                </div>
              </div>
              <button onClick={() => setCallSummaryRow(null)}
                className="w-8 h-8 rounded-none flex items-center justify-center text-slate-400 hover:text-slate-700 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
                <i className="fa-solid fa-xmark"></i>
              </button>
            </div>

            <div className="px-6 py-4 overflow-y-auto custom-scrollbar flex-1 min-h-0 space-y-4">
              {callSummaryLoading ? (
                <p className="text-sm text-slate-400 flex items-center gap-2 py-6">
                  <i className="fa-solid fa-spinner fa-spin"></i> Loading call details…
                </p>
              ) : !callSummaryRow.is_completed ? (
                <div className="border border-amber-200 dark:border-amber-900/40 bg-amber-50/60 dark:bg-amber-950/20 p-4 text-center">
                  <p className="text-sm font-semibold text-amber-800 dark:text-amber-300">Call summary is not available yet.</p>
                  <p className="text-xs text-amber-600 dark:text-amber-400 mt-1">The call is still processing.</p>
                </div>
              ) : (
                <>
                  <div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
                    {([
                      ['Candidate', callSummary?.candidate || callSummaryRow.candidate_name || '—'],
                      ['Job Title', callSummary?.job_title || callSummaryRow.job_title || '—'],
                      ['Call Status', CALL_STATUS_BADGES[(callSummary?.status || callSummaryRow.status) ?? '']?.label || '—'],
                      ['Call Date & Time', callSummary?.started_at || callSummaryRow.started_at || '—'],
                      ['Call Duration', (() => {
                        const s = callSummary?.duration ?? callSummaryRow.duration;
                        if (s == null) return '—';
                        const m = Math.floor(s / 60);
                        return m > 0 ? `${m}m ${s % 60}s` : `${s}s`;
                      })()],
                      ['Score', String(callSummary?.evaluation?.overall_score ?? callSummary?.score ?? callSummaryRow.score ?? '—')],
                    ] as [string, string][]).map(([label, value]) => (
                      <div key={label} className="border border-slate-200 dark:border-slate-800 p-3">
                        <p className="text-[9px] uppercase tracking-wider text-slate-400 font-bold">{label}</p>
                        <p className="text-sm font-bold text-slate-800 dark:text-white mt-1 break-words">{value}</p>
                      </div>
                    ))}
                  </div>

                  <div>
                    <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mb-2">Recommendation</p>
                    {(() => {
                      const rec = callSummary?.evaluation?.recommendation || callSummary?.recommendation || callSummaryRow.recommendation || '';
                      const meta: Record<string, { label: string; cls: string }> = {
                        QUALIFIED: { label: 'Qualified', cls: 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300' },
                        REVIEW: { label: 'Needs Review', cls: 'bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300' },
                        NOT_QUALIFIED: { label: 'Not Qualified', cls: 'bg-rose-50 text-rose-700 dark:bg-rose-950/40 dark:text-rose-300' },
                      };
                      const m = meta[rec];
                      return m
                        ? <span className={`text-[10px] font-bold px-2 py-0.5 ${m.cls}`}>{m.label}</span>
                        : <span className="text-xs text-slate-400">Not evaluated</span>;
                    })()}
                  </div>

                  {/* Prominent Call Recording Section — Positioned directly above AI Summary */}
                  <div className="border border-indigo-100 dark:border-indigo-900/40 bg-indigo-50/50 dark:bg-indigo-950/20 p-3.5 flex flex-wrap items-center justify-between gap-3">
                    <div className="flex items-center gap-3">
                      <div className="w-9 h-9 rounded-full bg-[#405189] text-white flex items-center justify-center shrink-0 text-base shadow-sm">
                        <i className="fa-solid fa-circle-play"></i>
                      </div>
                      <div>
                        <p className="text-[10px] uppercase tracking-wider font-extrabold text-[#405189] dark:text-indigo-300">Call Audio Recording</p>
                        <p className="text-xs text-slate-500 dark:text-slate-400">
                          {(callSummary?.recording_url || callSummaryRow.recording_url)
                            ? 'Full audio recording available for playback'
                            : 'Audio recording stream is currently processing or unavailable'}
                        </p>
                      </div>
                    </div>

                    {(callSummary?.recording_url || callSummaryRow.recording_url) ? (
                      <a
                        href={callSummary?.recording_url || callSummaryRow.recording_url}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="inline-flex items-center gap-2 bg-[#0ab39c] hover:bg-[#099885] text-white text-xs font-bold px-4 py-2 shadow-sm transition cursor-pointer"
                      >
                        <i className="fa-solid fa-play text-xs"></i> Play Audio Recording
                      </a>
                    ) : (
                      <span className="inline-flex items-center gap-1.5 text-xs text-slate-400 font-medium bg-slate-100 dark:bg-slate-800 px-3 py-1.5 border border-slate-200 dark:border-slate-700">
                        <i className="fa-solid fa-microphone-slash text-slate-400 text-xs"></i> Recording Unavailable
                      </span>
                    )}
                  </div>

                  <div>
                    <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mb-2">AI Summary</p>
                    {/* The call finished but Hunar hasn't produced the narrative
                        yet — say so rather than showing an empty box. */}
                    {!(callSummary?.evaluation?.summary || callSummary?.summary || callSummaryRow.summary) ? (
                      <p className="text-sm text-slate-600 dark:text-slate-300 border border-amber-200 dark:border-amber-900/40 bg-amber-50/60 dark:bg-amber-950/20 p-3">
                        Call summary is being processed.<br />Please try again in a few moments.
                      </p>
                    ) : (
                      <p className="text-sm text-slate-700 dark:text-slate-300 whitespace-pre-line border border-slate-200 dark:border-slate-800 p-3">
                        {callSummary?.evaluation?.summary || callSummary?.summary || callSummaryRow.summary}
                      </p>
                    )}
                  </div>

                  {/* Strengths / weaknesses — rendered only when Hunar returned them. */}
                  {((callSummary?.evaluation?.strengths?.length ?? 0) > 0
                    || (callSummary?.evaluation?.weaknesses?.length ?? 0) > 0) ? (
                    <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
                      {(['strengths', 'weaknesses'] as const).map((k) => (
                        (callSummary?.evaluation?.[k]?.length ?? 0) > 0 ? (
                          <div key={k}>
                            <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mb-2">{k}</p>
                            <ul className="text-xs text-slate-600 dark:text-slate-300 list-disc pl-4 space-y-1">
                              {callSummary!.evaluation![k].map((s, i) => <li key={i}>{s}</li>)}
                            </ul>
                          </div>
                        ) : null
                      ))}
                    </div>
                  ) : null}

                  {/* Key Responses — the per-question rubric Hunar returned. Shown
                      verbatim; nothing is synthesised when it is absent. */}
                  {(() => {
                    const rubric = (callSummary?.evaluation?.rubric ?? null) as Record<string, unknown> | null;
                    if (!rubric || typeof rubric !== 'object') return null;
                    // Hunar's own `result` object is stored under `hunar_result`;
                    // fall back to the rubric itself for evaluations recorded in
                    // the older shape. `call_summary` is already shown above, and
                    // fields Hunar marked "Not Covered" carry no information.
                    const source = (rubric.hunar_result && typeof rubric.hunar_result === 'object'
                      ? rubric.hunar_result
                      : rubric) as Record<string, unknown>;
                    const meta = (rubric.hunar_metadata && typeof rubric.hunar_metadata === 'object'
                      ? rubric.hunar_metadata
                      : {}) as Record<string, unknown>;
                    const entries = Object.entries(source).filter(([k, v]) => {
                      if (k === 'call_summary' || k === 'hunar_metadata' || k === 'hunar_result') return false;
                      const s = typeof v === 'string' ? v.trim() : v;
                      return s !== '' && s !== null && s !== undefined && s !== 'Not Covered';
                    });
                    const label = (k: string) =>
                      k.replace(/_/g, ' ').replace(/\b\w/g, (m) => m.toUpperCase());
                    // The recording is presented once, in the Call Audio Recording
                    // section at the top of this popup — no duplicate link here.
                    const metaBits = (['answered_by', 'engagement_status', 'call_ended_by', 'language'] as const)
                      .filter((k) => meta[k])
                      .map((k) => `${label(k)}: ${String(meta[k])}`);
                    if (entries.length === 0 && !metaBits.length) return null;
                    return (
                      <div>
                        <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mb-2">Key Responses</p>
                        {entries.length > 0 && (
                          <div className="border border-slate-200 dark:border-slate-800 divide-y divide-slate-100 dark:divide-slate-800">
                            {entries.map(([q, v]) => (
                              <div key={q} className="p-3">
                                <p className="text-[11px] font-bold text-slate-700 dark:text-slate-200 break-words">{label(q)}</p>
                                <p className="text-xs text-slate-600 dark:text-slate-300 mt-1 whitespace-pre-line break-words">
                                  {typeof v === 'string' || typeof v === 'number' ? String(v) : JSON.stringify(v)}
                                </p>
                              </div>
                            ))}
                          </div>
                        )}
                        {metaBits.length > 0 && (
                          <div className="flex flex-wrap items-center gap-x-3 gap-y-1 mt-2 text-[10px] font-semibold text-slate-500 dark:text-slate-400">
                            {metaBits.map((b) => <span key={b}>{b}</span>)}
                          </div>
                        )}
                      </div>
                    );
                  })()}

                  {(callSummary?.utterances?.length || callSummary?.transcript) ? (
                    <div>
                      <p className="text-[10px] uppercase tracking-wider font-extrabold text-slate-400 mb-2">Transcript</p>
                      <div className="border border-slate-200 dark:border-slate-800 p-3 max-h-64 overflow-y-auto custom-scrollbar space-y-2">
                        {callSummary?.utterances?.length ? (
                          callSummary.utterances.map((u) => (
                            <p key={u.sequence} className="text-xs text-slate-600 dark:text-slate-300">
                              <span className="font-bold text-[#405189] dark:text-indigo-300">{u.speaker}:</span> {u.message}
                            </p>
                          ))
                        ) : (
                          <p className="text-xs text-slate-600 dark:text-slate-300 whitespace-pre-line">{callSummary?.transcript}</p>
                        )}
                      </div>
                    </div>
                  ) : null}
                </>
              )}
            </div>

            <div className="flex justify-end px-6 py-4 border-t border-slate-100 dark:border-slate-800 shrink-0">
              <button onClick={() => setCallSummaryRow(null)}
                className="bg-[#405189] hover:bg-[#364574] text-white text-sm font-semibold px-5 py-2.5 rounded-none shadow-lg transition cursor-pointer">
                Close
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Notification preview & edit — shown before any Email/SMS/WhatsApp
          bulk send. Pre-filled with the default template; fully editable.
          The Send button here is what actually calls sendNotificationsDirectly. */}
      {previewOpen && (
        <div className="fixed inset-0 z-[150] flex items-center justify-center p-4">
          <div className="fixed inset-0 bg-slate-950/40 dark:bg-slate-950/80 backdrop-blur-sm" onClick={() => !bulkSending && setPreviewOpen(false)}></div>
          <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 w-full max-w-2xl rounded-none relative z-10 shadow-2xl text-slate-800 dark:text-slate-100 flex flex-col max-h-[90vh]">
            <div className="flex items-center justify-between px-6 pt-5 pb-4 border-b border-slate-100 dark:border-slate-800 shrink-0">
              <div className="flex items-center gap-3">
                <div className="w-10 h-10 rounded-none bg-indigo-50 dark:bg-indigo-950/40 text-indigo-600 dark:text-indigo-400 flex items-center justify-center">
                  <i className="fa-solid fa-envelope-open-text"></i>
                </div>
                <div>
                  <h3 className="text-base font-bold text-slate-900 dark:text-white leading-tight">Preview Notification</h3>
                  <p className="text-xs text-slate-400 mt-0.5">
                    {notifType} to {selectedEmailIds.length} selected candidate{selectedEmailIds.length === 1 ? '' : 's'}.
                  </p>
                </div>
              </div>
              <button onClick={() => !bulkSending && setPreviewOpen(false)} disabled={bulkSending !== null}
                className="w-8 h-8 rounded-none flex items-center justify-center text-slate-400 hover:text-slate-700 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer disabled:opacity-50">
                <i className="fa-solid fa-xmark"></i>
              </button>
            </div>

            <div className="px-6 py-4 overflow-y-auto custom-scrollbar flex-1 min-h-0 space-y-4">
              <p className="text-xs text-slate-500 dark:text-slate-400">
                Pick a template or edit below before sending. Use <code className="px-1 py-0.5 bg-slate-100 dark:bg-slate-800 rounded-none text-[11px]">{'{{candidate_name}}'}</code>, <code className="px-1 py-0.5 bg-slate-100 dark:bg-slate-800 rounded-none text-[11px]">{'{{job_title}}'}</code>, <code className="px-1 py-0.5 bg-slate-100 dark:bg-slate-800 rounded-none text-[11px]">{'{{company}}'}</code> to personalise.
              </p>

              {/* Template picker — from Master Data → Notification Templates */}
              <div>
                <label className="block text-[10px] uppercase font-bold text-slate-400 mb-1">Template</label>
                <select
                  value={templateId}
                  onChange={(e) => {
                    const id = e.target.value ? Number(e.target.value) : '';
                    setTemplateId(id);
                    const t = templates.find((x) => x.id === id);
                    const jobTitle = job?.title || 'the open position';
                    const company = job?.client_name || 'our team';
                    if (t) {
                      const vars = { job_title: jobTitle, company, location: job?.location || '' };
                      setPreviewSubject(fillTemplate(t.subject || `Regarding your application for ${jobTitle}`, vars));
                      setPreviewMessage(fillTemplate(t.body, vars));
                    }
                  }}
                  className="w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-none px-3 py-2 text-xs focus:outline-none text-slate-800 dark:text-white"
                >
                  {templates.length === 0 && <option value="">Default template (add under Master Data → Notification Templates)</option>}
                  {templates.map((t) => (
                    <option key={t.id} value={t.id}>{t.name}</option>
                  ))}
                </select>
              </div>

              {CHANNELS_FOR[notifType]?.includes('EMAIL') && (
                <div>
                  <label className="block text-xs font-bold uppercase tracking-wide text-slate-500 mb-1">Subject (email only)</label>
                  <input
                    value={previewSubject}
                    onChange={(e) => setPreviewSubject(e.target.value)}
                    disabled={bulkSending !== null}
                    className="w-full px-3 py-2.5 text-sm border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-900 focus:outline-none focus:border-indigo-500 disabled:opacity-50"
                  />
                </div>
              )}

              <div>
                <label className="block text-xs font-bold uppercase tracking-wide text-slate-500 mb-1">Message</label>
                <textarea
                  value={previewMessage}
                  onChange={(e) => setPreviewMessage(e.target.value)}
                  disabled={bulkSending !== null}
                  rows={8}
                  className="w-full px-3 py-2.5 text-sm border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-900 focus:outline-none focus:border-indigo-500 disabled:opacity-50 resize-y"
                />
              </div>
            </div>

            <div className="flex justify-end gap-3 px-6 py-4 border-t border-slate-100 dark:border-slate-800 shrink-0">
              <button onClick={() => setPreviewOpen(false)} disabled={bulkSending !== null}
                className="px-4 py-2 text-sm text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-white transition cursor-pointer disabled:opacity-50">
                Cancel
              </button>
              <button
                onClick={() => {
                  if (!previewMessage.trim()) { toast.warning('Message is required.'); return; }
                  const tpl = { subject: previewSubject, message: previewMessage };
                  // Close now so the existing live-progress bar (below the
                  // toolbar) is visible while the batches send in the background.
                  setPreviewOpen(false);
                  sendNotificationsDirectly(notifType, tpl);
                }}
                disabled={bulkSending !== null}
                className="bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white text-sm font-semibold px-5 py-2.5 rounded-none shadow-lg transition cursor-pointer flex items-center gap-2">
                {bulkSending !== null ? <i className="fa-solid fa-spinner fa-spin text-xs" /> : <i className="fa-solid fa-paper-plane text-xs" />}
                {bulkSending !== null ? 'Sending...' : `Send${selectedEmailIds.length > 0 ? ` (${selectedEmailIds.length})` : ''}`}
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}
