import type {
  AiSettings,
  AppNotification,
  Brand,
  BrandProduct,
  DataSource,
  ExternalThreadTarget,
  InspirationIdea,
  InspireChatMessage,
  InspireElement,
  InspireSession,
  InspireSessionSummary,
  InviteMemberBrief,
  InviteTreeNode,
  Job,
  Member,
  MentionItem,
  MyInviteNetwork,
  OwnPost,
  OutboxBundle,
  Persona,
  PlacementSettings,
  ResearchHit,
  Role,
  ScoutHomeworkRecord,
  ScoutPost,
  ScoutRunBrief,
  ScoutScanContext,
  ThreadPlay,
  ThreadsAccount,
  TokenPair,
  TrendItem,
  TrendKind,
  ViralAnalysis,
} from "../domain/types";
import type { AdminUserListPage, MemberAdminView } from "../lib/tenantUsers";
import type { PlanPurchase } from "../lib/planPurchase";
import type {
  PlanId,
  TenantAnalyticsQuery,
  TenantUsageAnalytics,
  TenantUsageSummary,
  UsageEvent,
  UsageMemberPrefs,
  UsageMonthSummary,
} from "../lib/usageMeter";

export type MemberProfilePatch = {
  display_name?: string;
  nickname?: string;
  full_name?: string;
  email?: string;
  phone?: string;
  bio?: string;
  timezone?: string;
  notify_email?: boolean;
  /**
   * 頭像 URL（data URL 或 https）。
   * 傳 `null` 清除；`undefined` 不改。
   */
  avatar_url?: string | null;
  gender_code?: number;
  birthdate?: number;
  national?: string;
  address?: string;
  post_code?: string;
  preferred_language?: string;
  currency?: string;
  /** skipped | completed；只寫一次 */
  onboarding_status?: "skipped" | "completed";
  /** 改密時必填 */
  current_password?: string;
  new_password?: string;
};

export type AuthRepo = {
  login(email: string, password: string): Promise<{ tokens: TokenPair; member: Member }>;
  logout(): Promise<void>;
  /**
   * null 代表「確定未登入」（沒 token 或後端回 401）。
   * 暫時性失敗（斷網、後端重啟、5xx）必須 throw，不可回 null，
   * 否則呼叫端會把使用者當成登出而踢回登入頁。
   */
  me(): Promise<Member | null>;
  /** 更新自己的會員資料（mock 寫 session） */
  updateProfile(patch: MemberProfilePatch): Promise<Member>;
  /**
   * 申請重設密碼。mock 一律回成功（不洩漏帳號是否存在）；
   * 若為已知帳號會給 mock_reset_path 方便本機測試。
   */
  requestPasswordReset(email: string): Promise<{
    ok: true;
    message: string;
  }>;
  /**
   * 用驗證碼／token 設定新密碼。
   * email 必填（live）；mock 可只靠 token。
   */
  resetPassword(
    token: string,
    newPassword: string,
    email?: string,
  ): Promise<{ ok: true; message: string }>;
  /** 寄送信箱驗證碼（需已登入） */
  sendEmailVerificationCode(): Promise<{
    ok: true;
    message: string;
  }>;
  /** 輸入驗證碼完成信箱驗證 */
  verifyEmail(code: string): Promise<Member>;
  /**
   * 上傳圖片到物件儲存（MinIO/S3），回傳公開 URL。
   * content 為 data URL；kind 預設 avatar。
   */
  /** kind: avatar 永久；temp 發文暫存（發成功／取消 outbox 後刪）；other 相容 */
  uploadImage(content: string, kind?: "avatar" | "other" | "temp"): Promise<{ url: string; key?: string }>;
};

/** 管理者新增島民 */
export type AdminCreateMemberInput = {
  display_name: string;
  email: string;
  password?: string;
  roles?: Role[];
  email_verified?: boolean;
  bio?: string;
  /** 邀請人 uid；未傳 = 掛建立者；null = 根 */
  parent_uid?: string | null;
};

/**
 * 邀請關係（mock 支線）。
 * 一般會員：邀請碼 + 邀請人 + 直邀；可補填邀請碼。
 * 管理員：焦點鑽取 + 調整歸屬。
 */
export type InviteRepo = {
  /** 目前登入者的邀請網絡 */
  getMyNetwork(): Promise<MyInviteNetwork>;
  /**
   * 會員補填邀請碼（僅尚無邀請人時）。
   * 成功回傳更新後的網絡。
   */
  applyInviteCode(code: string): Promise<MyInviteNetwork>;
  /** 管理員：邀請森林 */
  getTree(): Promise<InviteTreeNode[]>;
  /** 管理員：改邀請人（null = 獨立根） */
  moveMember(uid: string, newParentUid: string | null): Promise<InviteMemberBrief>;
  /** 管理員：可當邀請人的候選（排除 subtree） */
  listParentCandidates(excludeSubtreeRootUid?: string): Promise<InviteMemberBrief[]>;
};

/** 管理員：島民列表與可寫操作 */
export type AdminUsersRepo = {
  /** @deprecated 請用 listUsersPage */
  listUsers(): Promise<MemberAdminView[]>;
  /** 分頁列表 page 從 1 起；query 可搜名稱 / Email / uid */
  listUsersPage(opts?: {
    page?: number;
    pageSize?: number;
    query?: string;
  }): Promise<AdminUserListPage>;
  getUser(uid: string): Promise<MemberAdminView | null>;
  /** 新增島民（回傳臨時密碼） */
  createMember(
    input: AdminCreateMemberInput,
  ): Promise<{ user: MemberAdminView; temporary_password: string }>;
  /** 停權 / 復權 */
  setSuspended(uid: string, suspended: boolean): Promise<MemberAdminView>;
  /** 改驗證狀態 */
  setEmailVerified(uid: string, verified: boolean): Promise<MemberAdminView>;
  /** 指派角色（member / admin） */
  setRoles(uid: string, roles: Role[]): Promise<MemberAdminView>;
  /**
   * 幫使用者重設密碼。未傳 newPassword 則產生臨時密碼。
   * 回傳 temporary_password 僅此次顯示。
   */
  resetPassword(
    uid: string,
    newPassword?: string,
  ): Promise<{ user: MemberAdminView; temporary_password: string }>;
};

export type AccountsRepo = {
  list(): Promise<ThreadsAccount[]>;
  /** mock 專用：假連帳 */
  createMock(): Promise<ThreadsAccount>;
  /** live：OAuth start（回授權 URL；fake provider 會指回 callback） */
  oauthStart(): Promise<{ authorize_url: string; state: string }>;
  remove(id: string): Promise<void>;
  refreshSession(id: string): Promise<ThreadsAccount>;
};

export type PlaysRepo = {
  list(): Promise<ThreadPlay[]>;
  listByPost(ownPostId: string): Promise<ThreadPlay[]>;
  /** 依外部 Threads 連結列出方案（URL 正規化後比對） */
  listByExternalUrl(url: string): Promise<ThreadPlay[]>;
  /** 貼連結 → mock 解析目標貼文 */
  resolveExternalLink(url: string): Promise<ExternalThreadTarget>;
  get(id: string): Promise<ThreadPlay | null>;
  save(play: ThreadPlay): Promise<ThreadPlay>;
  remove(id: string): Promise<void>;
  submit(id: string): Promise<OutboxBundle>;
  /** 真 AI 產劇本一步（reply / root） */
  generateStep(opts: {
    personaId?: string;
    context: string;
    topic?: string;
    speakerLabel?: string;
    isLead?: boolean;
    mode?: "reply" | "root";
  }): Promise<string>;
  /**
   * 一次產完整劇本（背景 Job）。
   * 完成後 play 已寫入 DB，前端應 reload play。
   */
  generateScript(playId: string): Promise<{ jobId: string; async: boolean }>;
};

export type OutboxRepo = {
  list(): Promise<OutboxBundle[]>;
  get(id: string): Promise<OutboxBundle | null>;
  simulateSuccess(id: string): Promise<OutboxBundle>;
  simulateRootFail(id: string): Promise<OutboxBundle>;
  retryStep(bundleId: string, stepId: string): Promise<OutboxBundle>;
  /** 刪除整筆發送佇列項目 */
  remove(id: string): Promise<void>;
};

/** 任務列表分桶：定期排程 / 執行中 / 歷史 */
export type JobListTab = "recurring" | "active" | "history";

export type JobListQuery = {
  tab?: JobListTab;
  page?: number;
  pageSize?: number;
};

export type JobListResult = {
  list: Job[];
  pagination: {
    page: number;
    pageSize: number;
    total: number;
    totalPages: number;
  };
};

export type ListPage<T> = {
  list: T[];
  pagination: {
    page: number;
    pageSize: number;
    total: number;
    totalPages: number;
  };
};

export type ScoutRunListQuery = {
  page?: number;
  pageSize?: number;
  brandId?: string | null;
  mode?: import("../domain/types").ScoutMode;
};

export type ScoutResultListQuery = {
  page?: number;
  pageSize?: number;
};

export type JobsRepo = {
  /**
   * 分頁列表。傳 query 時走 tab+page；無 query 時預設 active 前 50 筆（相容舊呼叫）。
   * 回傳完整 JobListResult；若只要陣列可用 .list。
   */
  list(query?: JobListQuery): Promise<JobListResult>;
  get(id: string): Promise<Job | null>;
  startDemo(): Promise<Job>;
  /** 手動刪除（執行中不可刪；後端終態約 2 天後也會自動清） */
  remove(id: string): Promise<void>;
};

export type NotificationsRepo = {
  list(): Promise<AppNotification[]>;
  unreadCount(): Promise<number>;
  markRead(id: string): Promise<void>;
  markAllRead(): Promise<void>;
};

/** Threads OAuth 平台狀態（App Secret 不回傳） */
export type ThreadsPlatformStatus = {
  provider: "fake" | "meta" | string;
  configured: boolean;
  oauth_ready: boolean;
  connect_path: string;
  /** Meta Valid OAuth Redirect URIs 用 */
  callback_url: string;
  public_web_base: string;
  app_id_masked?: string;
  hint: string;
};

export type SettingsRepo = {
  getDataSource(): DataSource;
  setDataSource(ds: DataSource): void;
  /**
   * 一次拿：已存設定（含 selected model）+ 模型清單。
   * provider 可選：預覽另一個 provider 的 models（不改存檔）。
   */
  getAi(provider?: string): Promise<AiSettings>;
  saveAi(patch: Partial<AiSettings> & { api_key?: string; research_api_key?: string }): Promise<AiSettings>;
  getPlacement(): Promise<PlacementSettings>;
  savePlacement(
    patch: Partial<PlacementSettings> & { brave_api_key?: string; exa_api_key?: string },
  ): Promise<PlacementSettings>;
  /** @deprecated 優先用 getAi(provider) 一併取 models + selected_model */
  listModels(provider: string): Promise<string[]>;
  /** 平台 Threads App 是否已接 Meta（設定頁狀態卡） */
  getThreadsStatus(): Promise<ThreadsPlatformStatus>;
};

export type PersonasRepo = {
  list(): Promise<Persona[]>;
  get(id: string): Promise<Persona | null>;
  getActiveId(): Promise<string>;
  setActiveId(id: string): Promise<void>;
  save(persona: Persona): Promise<Persona>;
  remove(id: string): Promise<void>;
  analyzeFromText(id: string, rawText: string, sourceLabel?: string): Promise<Persona>;
  analyzeFromAccount(id: string, username: string): Promise<Persona>;
};

export type OwnPostsRepo = {
  list(accountId?: string): Promise<OwnPost[]>;
  lastSyncedAt(): Promise<number | null>;
  sync(accountId: string): Promise<OwnPost[]>;
  /** 點開貼文後才載留言（避免同步時一次打爆 API） */
  loadReplies(postId: string): Promise<OwnPost>;
  generateReply(opts: {
    postId: string;
    replyId?: string;
    personaId?: string;
  }): Promise<string>;
  sendReply(opts: {
    postId: string;
    replyId?: string;
    text: string;
    /** 用哪個帳號送出（mock 寫入 username） */
    accountId?: string;
    /** 附圖 URL（mock 記張數／可選縮圖） */
    imageUrls?: string[];
  }): Promise<OwnPost>;
  manageReply(opts: { postId: string; replyId: string; hide: boolean }): Promise<OwnPost>;
  setReplyControl(opts: { postId: string; replyControl: string }): Promise<OwnPost>;
  analyzePost(postId: string): Promise<OwnPost>;
  generateFromFormula(postId: string, personaId?: string): Promise<{ title: string; topic: string; root: string }>;
};

export type MentionsRepo = {
  list(accountId?: string): Promise<MentionItem[]>;
  /** 從 Threads Graph 同步「@我」提及（需 threads_manage_mentions） */
  sync(accountId: string): Promise<MentionItem[]>;
  generateReply(id: string, personaId?: string): Promise<MentionItem>;
  markReplied(id: string, text?: string, imageUrls?: string[]): Promise<MentionItem>;
  skip(id: string): Promise<MentionItem>;
};

export type InspirationRepo = {
  list(): Promise<InspirationIdea[]>;
  /** 預設靈感榜：Threads 熱標（提示用） */
  listTrends(kind?: TrendKind | "all"): Promise<TrendItem[]>;
  refreshTrends(kind?: TrendKind | "all"): Promise<TrendItem[]>;
  /** 元素庫 */
  listElements(): Promise<InspireElement[]>;
  saveElement(el: InspireElement): Promise<InspireElement>;
  removeElement(id: string): Promise<void>;
  /** 目前作用中的 session */
  getSession(): Promise<InspireSession>;
  /**
   * 相容舊 API：開新對話並切過去（舊 session 保留）。
   * 建議 UI 用 createSession。
   */
  clearSession(): Promise<InspireSession>;
  /** 多對話列表 */
  listSessions(): Promise<InspireSessionSummary[]>;
  createSession(title?: string): Promise<InspireSession>;
  activateSession(id: string): Promise<InspireSession>;
  deleteSession(id: string): Promise<InspireSession>;
  /**
   * 聊天／產文。pinnedIds 為本輪套用元素；會寫回 session。
   * mode=generate 時 assistant 帶 draft.body（乾淨正文）。
   */
  chat(opts: {
    message: string;
    pinnedIds: string[];
    mode: "chat" | "generate";
    personaId?: string;
    sessionId?: string;
    /** chat：本輪先用 Exa 查資料 */
    useWeb?: boolean;
    /** generate：待改寫素材 */
    material?: string;
  }): Promise<{ session: InspireSession; messages: InspireChatMessage[] }>;
  /**
   * 串流聊天／產文。onDelta 每收到一段正文就回呼；結束回完整 session。
   * 中止時 reject `AbortError`（`isAbortError()` 可判斷），呼叫端不需當成失敗顯示。
   */
  chatStream(
    opts: {
      message: string;
      pinnedIds: string[];
      mode: "chat" | "generate";
      personaId?: string;
      sessionId?: string;
      useWeb?: boolean;
      material?: string;
      /** 使用者按停止或元件卸載時中止；不帶則跑到結束。 */
      signal?: AbortSignal;
    },
    onDelta: (chunk: string) => void,
  ): Promise<{
    session: InspireSession;
    /** 實際送 AI 的完整 prompt（與預覽同組裝） */
    prompt?: string;
    fingerprint?: string;
    charCount?: number;
    runeCount?: number;
    sections?: string[];
  }>;
  /**
   * 觀測：後端用與送出相同的 buildInspirePrompt 組裝真實 prompt（不呼叫 AI、不扣額）。
   * fingerprint 與送出後 done 事件的 prompt_fingerprint 可比對是否一致。
   */
  previewPrompt(opts: {
    message: string;
    pinnedIds: string[];
    mode: "chat" | "generate";
    personaId?: string;
    sessionId?: string;
    material?: string;
  }): Promise<{
    prompt: string;
    mode: string;
    sections: string[];
    blocks: Array<{ title: string; body: string }>;
    fingerprint: string;
    charCount: number;
    runeCount: number;
    pinned: Array<{ id: string; kind: string; title: string; body: string }>;
    note?: string;
  }>;
};

export type ResearchRepo = {
  search(query: string): Promise<ResearchHit[]>;
};

export type MediaRepo = {
  generateImage(prompt: string): Promise<{ id: string; url: string; prompt: string }>;
  /** 本機選檔 → 可預覽附圖（Phase B mock，不真上傳） */
  attachLocal(files: FileList | File[]): Promise<{ id: string; url: string; name?: string }[]>;
};

export type PersonaPreviewResult = {
  topic: string;
  topic_source: "news" | "manual" | "fallback" | string;
  post_text: string;
  reply_text: string;
  notes?: string;
};

export type ComposeRepo = {
  /**
   * 仿寫：走背景 Job。
   * - async=true → 回 jobId，前端輪詢 job 取 payload.result_text
   * - async=false → 同步 text（測試降級）
   */
  mimic(
    sourceText: string,
    personaId?: string,
    direction?: string,
    structureNotes?: string,
  ): Promise<{ text?: string; jobId?: string; async: boolean }>;
  /** 依指紋真 LLM 試產主貼 + 回文（可抓新聞話題） */
  personaPreview(opts: {
    personaId: string;
    topic?: string;
    useNews?: boolean;
  }): Promise<PersonaPreviewResult>;
  analyzeViral(text: string): Promise<ViralAnalysis>;
  /** 單篇送出 Outbox */
  publishSingle(opts: {
    accountId: string;
    text: string;
    title?: string;
    imageUrls?: string[];
    /**
     * 預計發送時間 unix nanoseconds UTC。
     * 未傳或 ≤ now → 立即／盡快排程。
     */
    schedule_start_at?: number;
    /** Threads 話題標籤（topic_tag，可不加 #） */
    topicTag?: string;
    /** Threads reply_control，發文時設定誰可以回覆 */
    replyControl?: string;
  }): Promise<OutboxBundle>;
};

export type ScoutRepo = {
  listBrands(): Promise<Brand[]>;
  get(id: string): Promise<Brand | null>;
  getActiveBrandId(): Promise<string>;
  setActiveBrandId(id: string): Promise<void>;
  /** 對齊舊 BrandsPage：建立牌子 */
  createBrand(input?: { display_name?: string; brief?: string }): Promise<Brand>;
  saveBrand(brand: Brand): Promise<Brand>;
  removeBrand(id: string): Promise<void>;
  listProducts(brandId: string): Promise<BrandProduct[]>;
  /** 全部產品（探查下拉用） */
  listAllProducts(): Promise<BrandProduct[]>;
  getProduct(id: string): Promise<BrandProduct | null>;
  saveProduct(product: BrandProduct): Promise<BrandProduct>;
  removeProduct(id: string): Promise<void>;
  /**
   * 反著做：貼商品連結 → 抓取／推估後回填表單草稿（尚未存檔）。
   * Phase B mock；live 應走後端爬頁。
   */
  importProductFromUrl(url: string): Promise<{
    label: string;
    product_context: string;
    pain_points: string[];
    match_tags: string[];
    placement_url: string;
    source_note: string;
  }>;
  /**
   * 意圖 + 可選產品 → 可審知識 brief（痛點／周邊／掃描詞）
   * 無產品 = theme 模式
   */
  prepareBrief(opts: {
    intent: string;
    brandId?: string | null;
    productId?: string | null;
    /** demand＝找需求痛點；provider＝找解法提供者；activity＝非推廣主題互動 */
    purpose?: "demand" | "provider" | "activity";
    /**
     * 是否做完整上網功課（摘要＋分層來源）。
     * 預設 false：只組痛點／掃描詞，不擋海巡；背景再 deep 補齊。
     */
    deep?: boolean;
  }): Promise<ScoutRunBrief>;
  /** 依 brief（含使用者勾選後的 scan_terms）建立背景海巡任務。 */
  runScanFromBrief(brief: ScoutRunBrief): Promise<{ job: Job; run: import("../domain/types").ScoutRun }>;
  listRuns(query?: ScoutRunListQuery): Promise<ListPage<import("../domain/types").ScoutRun>>;
  listRunPosts(
    runId: string,
    query?: ScoutResultListQuery,
  ): Promise<
    ListPage<ScoutPost> & {
      run: import("../domain/types").ScoutRun;
    }
  >;
  /** @deprecated 用 prepareBrief */
  getScanContext(brandId: string, productId?: string | null): Promise<ScoutScanContext>;
  /** 列出命中；brandId 空 = 全部（含主題模式） */
  listPosts(brandId?: string | null): Promise<ScoutPost[]>;
  /** @deprecated 用 runScanFromBrief */
  runScan(
    brandId: string,
    productId?: string | null,
    extraTerms?: string[],
  ): Promise<{ job: Job }>;
  draftOutreach(postId: string, personaId?: string): Promise<ScoutPost>;
  skipOutreach(postId: string): Promise<ScoutPost>;
  markPublished(postId: string): Promise<ScoutPost>;
  /** 升級為商機：複製成 Opportunity，不改 outreach_status */
  promoteToOpportunity(postId: string): Promise<{
    opportunity_id: string;
    status: string;
    intent_band?: string;
    intent_score?: number;
  }>;
  /**
   * 模擬發送外展回覆：寫入草稿、標記 published、可帶帳號
   */
  sendOutreach(opts: {
    postId: string;
    text: string;
    accountId?: string;
    personaId?: string;
  }): Promise<ScoutPost>;
  /** 刪除單則命中 */
  removePost(postId: string): Promise<void>;
  /** 刪除同一主題分組下全部命中 */
  removeTheme(themeKey: string): Promise<void>;
  /** 刪除獨立海巡批次及其結果 */
  removeRun(runId: string): Promise<void>;
  /** 已完成的功課列表（持久化） */
  listHomework(): Promise<ScoutHomeworkRecord[]>;
  /** 存一輪功課（同 theme_key 覆蓋） */
  saveHomework(record: ScoutHomeworkRecord): Promise<ScoutHomeworkRecord>;
  getHomework(themeKey: string): Promise<ScoutHomeworkRecord | null>;
  removeHomework(themeKey: string): Promise<void>;
};

export type UsageRepo = {
  /** 目前登入者本月摘要 + 明細 */
  getSummary(monthKey?: string): Promise<UsageMonthSummary>;
  listEvents(limit?: number): Promise<UsageEvent[]>;
  getPlanId(): Promise<PlanId>;
  /** 讀方案／無限；uid 需管理員權限，省略則為自己 */
  getMemberPrefs(uid?: string): Promise<UsageMemberPrefs>;
  /** 管理員：設某人方案／無限 */
  setMemberPrefs(uid: string, patch: Partial<UsageMemberPrefs>): Promise<UsageMemberPrefs>;
  /** 管理員：全體本月用量 */
  getTenantSummary(monthKey?: string): Promise<TenantUsageSummary>;
  /** 管理員：全體區間分析（日／月／年 + 購買 vs 消耗） */
  getTenantAnalytics(query?: TenantAnalyticsQuery): Promise<TenantUsageAnalytics>;
  createCheckoutSession(plan_id: PlanId, request_id: string): Promise<BillingCheckoutSession>;
  getCheckoutSession(id: string): Promise<BillingCheckoutStatus>;
  getBillingSubscription(): Promise<BillingSubscription>;
  createBillingPortalSession(): Promise<BillingPortalSession>;
  /** 自己的購買紀錄 */
  listMyPurchases(limit?: number): Promise<PlanPurchase[]>;
};

export type BillingCheckoutSession = {
  id: string;
  session_id: string;
  url: string;
  expires_at: number;
};

export type BillingCheckoutStatus = {
  id: string;
  plan_id: PlanId;
  checkout_status: string;
  payment_status: string;
  fulfillment_status: string;
};

export type BillingSubscription = {
  plan_id: PlanId;
  status: string;
  current_period_end: number;
  cancel_at_period_end: boolean;
};

export type BillingPortalSession = { url: string };

export type GrowthRepo = {
  getOutcomeSummary(range?: "week" | "month"): Promise<import("../domain/types").OutcomeSummary>;
  listOutcomes(page?: number, pageSize?: number): Promise<import("../domain/types").OutcomeEvent[]>;
  reportConversion(
    id: string,
    amount?: number,
    note?: string,
    currency?: string,
  ): Promise<import("../domain/types").OutcomeEvent>;
  getLatestCheckup(): Promise<import("../domain/types").WeeklyCheckup>;
  generateCheckupNow(): Promise<import("../domain/types").WeeklyCheckup>;
  listAccountHealth(): Promise<import("../domain/types").AccountHealth[]>;
  listWorkspaces(): Promise<{
    list: import("../domain/types").Workspace[];
    current_workspace_id: string;
  }>;
  switchWorkspace(id: string): Promise<{
    list: import("../domain/types").Workspace[];
    current_workspace_id: string;
  }>;
  createWorkspace(name: string): Promise<import("../domain/types").Workspace>;
};

/**
 * demand-radar 加值層。契約見 docs/product/demand-radar/spec.md §5.2。
 * 尚未實作的能力後端回 501010，`apiRequest` 會丟 ApiError，呼叫端不得當成空結果。
 */
export type RadarRepo = {
  getServiceProfile(): Promise<import("../domain/types").ServiceProfile>;
  saveServiceProfile(
    patch: Omit<import("../domain/types").ServiceProfile, "exists" | "updated_at">,
  ): Promise<import("../domain/types").ServiceProfile>;
  listWatches(
    page?: number,
    pageSize?: number,
    status?: string,
    contextMode?: import("../domain/types").RadarWatchContextMode,
    brandId?: string,
    productId?: string,
  ): Promise<{
    list: import("../domain/types").RadarWatch[];
    total: number;
    active_count: number;
    max_active: number;
    profile_exists: boolean;
  }>;
  createWatch(input: {
    terms: string[];
    exclude_terms?: string[];
    regions?: string[];
    enabled?: boolean;
    brand_id?: string;
    product_id?: string;
  }): Promise<import("../domain/types").RadarWatch>;
  assignWatchProduct(
    id: string,
    brandId: string,
    productId: string,
  ): Promise<import("../domain/types").RadarWatch>;
  updateWatch(
    id: string,
    patch: { terms?: string[]; exclude_terms?: string[]; regions?: string[] },
  ): Promise<import("../domain/types").RadarWatch>;
  pauseWatch(id: string): Promise<import("../domain/types").RadarWatch>;
  resumeWatch(id: string): Promise<import("../domain/types").RadarWatch>;
  archiveWatch(id: string): Promise<void>;
  /** 永久刪除已封存的巡邏設定；歷史商機與巡邏紀錄保留。 */
  deleteArchivedWatch(id: string): Promise<void>;
  suggestWatchTerms(
    input?: number | { limit?: number; brand_id?: string; product_id?: string },
  ): Promise<import("../domain/types").WatchTermSuggestion[]>;
  triggerWatchSweep(id: string): Promise<{ job_id: string; sweep_id?: string }>;
  getToday(filter?: {
    brand_id?: string;
    product_id?: string;
    fit_band?: string;
  }): Promise<import("../domain/types").RadarToday>;
  listOpportunities(filter?: {
    page?: number;
    pageSize?: number;
    band?: string;
    status?: string;
    watch_id?: string;
    brand_id?: string;
    product_id?: string;
    fit_band?: string;
    match_state?: string;
    review_state?: import("../domain/types").OpportunityReviewState;
    time_scope?: import("../domain/types").OpportunityTimeScope;
    priority_band?: import("../domain/types").OpportunityPriorityBand;
    sort?: string;
    from?: number;
    to?: number;
  }): Promise<{ list: import("../domain/types").Opportunity[]; total: number }>;
  getOpportunity(id: string): Promise<import("../domain/types").Opportunity>;
  acceptOpportunity(id: string): Promise<{
    opportunity_id: string;
    contact_id?: string;
    status: string;
  }>;
  dismissOpportunity(id: string, reason?: string): Promise<import("../domain/types").Opportunity>;
  updateOpportunityReviewState(
    id: string,
    patch: {
      state: import("../domain/types").OpportunityReviewState;
      removal_reason?: import("../domain/types").OpportunityRemovalReason;
      removal_note?: string;
      duplicate_of_opportunity_id?: string;
    },
  ): Promise<import("../domain/types").Opportunity>;
  overrideOpportunity(
    id: string,
    patch: { band?: string; status?: string; note?: string },
  ): Promise<import("../domain/types").Opportunity>;
  setPrimaryProduct(
    id: string,
    productId: string,
    reason: string,
  ): Promise<import("../domain/types").Opportunity>;
  listReplies(opportunityId: string): Promise<import("../domain/types").ReplyVariant[]>;
  createReply(
    opportunityId: string,
    variant: import("../domain/types").ReplyVariantKind,
  ): Promise<import("../domain/types").ReplyVariant>;
  /**
   * channel: outbox | manual_copy；dm 僅 manual_copy。
   * accountId 在 channel=outbox 時必填：要用哪個已連的 Threads 帳號真的送出。
   */
  markReplyUsed(
    opportunityId: string,
    replyId: string,
    channel: "outbox" | "manual_copy",
    accountId?: string,
  ): Promise<{
    reply: import("../domain/types").ReplyVariant;
    health_advice?: string;
  }>;
  listSweeps(
    page?: number,
    pageSize?: number,
    watchId?: string,
  ): Promise<{ list: import("../domain/types").RadarSweep[]; total: number }>;
  /** 手動匯入商機（P1）：貼 Threads／Facebook 貼文網址＋內文或 CSV 批次，走同一套五問判定 */
  importOpportunities(
    items: import("../domain/types").ManualImportItem[],
    context?: { brand_id?: string; product_id?: string },
  ): Promise<{ results: import("../domain/types").ManualImportResult[] }>;
  /** 立即探索：短詞 fan-out 搜尋 → 五問判定，寫入今日商機 */
  exploreOpportunities(
    terms: string[],
    context?: { brand_id?: string; product_id?: string; preview_id?: string; credit_ceiling?: number; demand_input_version?: string; demand_map_version?: number },
  ): Promise<import("../domain/types").ExploreResult>;
  getDemandMap(productId: string): Promise<import("../domain/types").DemandMap>;
  updateDemandMap(productId: string, patch: Omit<import("../domain/types").DemandMap, "product_id" | "updated_at"> & { expected_map_version: number }): Promise<import("../domain/types").DemandMap>;
  enrichDemandMap(productId: string, input: { preview_id: string; credit_ceiling: number; expected_map_version: number }): Promise<import("../domain/types").DemandMap>;
  getCostPreview(input: { action: string; watch_id?: string; product_id?: string; candidate_limit?: number }): Promise<import("../domain/types").CostPreview>;
};

export type CrmRepo = {
  listContacts(filter?: {
    page?: number;
    pageSize?: number;
    query?: string;
    stage?: string;
    /** 留空＝不篩選；true／false 才篩 */
    follow_up?: boolean;
    band?: string;
    sort?: "last_touch_at" | "intent_score";
  }): Promise<{
    list: import("../domain/types").Contact[];
    total: number;
    stage_counts: import("../domain/types").ContactStageCount[];
  }>;
  getContact(
    id: string,
    page?: number,
    pageSize?: number,
  ): Promise<import("../domain/types").ContactDetail>;
  /** 從工作名單移除；保留商機、接觸與成交歷史。 */
  deleteContact(id: string): Promise<void>;
  /** 允許任意跳轉，含回退、跳級與終態重開 */
  updateStage(
    id: string,
    stage: import("../domain/types").ContactStage,
    note?: string,
  ): Promise<import("../domain/types").Contact>;
  setFollowUp(
    id: string,
    needsFollowUp: boolean,
    days?: number,
  ): Promise<import("../domain/types").Contact>;
  addNote(id: string, body: string): Promise<import("../domain/types").ContactTouch>;
  mergeContact(id: string, sourceContactId: string): Promise<import("../domain/types").Contact>;
  unmergeContact(id: string, mergedContactId: string): Promise<import("../domain/types").Contact>;
  /** 成交一律由人回報；系統不自動判定 */
  reportConversion(
    id: string,
    input: { amount?: number; currency?: string; note?: string },
  ): Promise<{ contact_id: string; outcome_id?: string; stage: string }>;
  updateConversion(
    id: string,
    input: { amount?: number; currency?: string; note?: string },
  ): Promise<{ contact_id: string; outcome_id?: string; stage: string }>;
  deleteConversion(id: string): Promise<void>;
  listFollowUps(
    page?: number,
    pageSize?: number,
    status?: string,
  ): Promise<{ list: import("../domain/types").FollowUp[]; total: number }>;
  doneFollowUp(id: string): Promise<import("../domain/types").FollowUp>;
  snoozeFollowUp(id: string, days: number): Promise<import("../domain/types").FollowUp>;
  generateFollowUpMessage(id: string): Promise<string>;
  getStats(from?: number, to?: number): Promise<import("../domain/types").CrmStats>;
};

export type Repos = {
  dataSource: DataSource;
  auth: AuthRepo;
  accounts: AccountsRepo;
  plays: PlaysRepo;
  outbox: OutboxRepo;
  jobs: JobsRepo;
  notifications: NotificationsRepo;
  settings: SettingsRepo;
  personas: PersonasRepo;
  ownPosts: OwnPostsRepo;
  mentions: MentionsRepo;
  inspiration: InspirationRepo;
  research: ResearchRepo;
  media: MediaRepo;
  compose: ComposeRepo;
  scout: ScoutRepo;
  usage: UsageRepo;
  adminUsers: AdminUsersRepo;
  invite: InviteRepo;
  growth: GrowthRepo;
  radar: RadarRepo;
  crm: CrmRepo;
};
