//! Serializable research-domain entities, enums, and command DTOs.

use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum WorkspaceScope {
    #[default]
    SingleCompany,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum ResearchStage {
    #[default]
    Capture,
    Organize,
    Thesis,
    Drafting,
    Monitor,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum WorkspaceViewKind {
    #[default]
    Canvas,
    Kanban,
    Graph,
    ThesisBuilder,
    MemoDrafting,
    EvidenceTrace,
    CatalystRiskMap,
    Timeline,
    Model,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum NoteType {
    #[default]
    Fact,
    Quote,
    ManagementSignal,
    Claim,
    Thesis,
    SubThesis,
    Risk,
    Catalyst,
    ValuationPoint,
    ScenarioAssumption,
    IndustryObservation,
    CompetitorComparison,
    Question,
    Contradiction,
    Uncertainty,
    FollowUpTask,
    SourceReference,
    EventTakeaway,
    ChannelCheck,
    MosaicInsight,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum AnalystStatus {
    #[default]
    Captured,
    ReviewQueue,
    Reviewed,
    Accepted,
    NeedsFollowUp,
    Dismissed,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceStatus {
    #[default]
    Unsourced,
    SourceLinked,
    Quoted,
    Corroborated,
    Inferred,
    Contradicted,
    Stale,
    Superseded,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum GhostStatus {
    #[default]
    None,
    CandidateInput,
    GhostGenerated,
    GhostPromoted,
    GhostDismissed,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum ThesisStatus {
    #[default]
    None,
    CandidateSupport,
    CandidateCore,
    AcceptedSupport,
    AcceptedCore,
    BullCase,
    BearCase,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum TimeHorizon {
    NearTerm,
    NextEarnings,
    TwelveMonth,
    MultiYear,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ScenarioKind {
    Base,
    Bull,
    Bear,
    DownsideCase,
    UpsideCase,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum NotePriority {
    Low,
    #[default]
    Normal,
    High,
    Critical,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum StaleReason {
    NewFiling,
    NewGuidance,
    NewEarnings,
    SourceExpired,
    NoteUpdated,
    SupersededByAnalyst,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum SourceKind {
    Filing,
    Transcript,
    Article,
    NewsFeed,
    Model,
    #[default]
    Manual,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum FreshnessBucket {
    Fresh,
    Aging,
    Stale,
}

#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceStrength {
    PrimaryVerified,
    PrimaryExcerpt,
    SecondaryReputable,
    SecondaryUnverified,
    AnalystModel,
    ManualUnsourced,
    AiInferenceOnly,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ProvenanceActor {
    Manual,
    AiEnrichment,
    GhostConversion,
    Import,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum CaptureMethod {
    QuickEntry,
    TranscriptClip,
    FilingExtract,
    NewsImport,
    ManualLink,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ModelInfo {
    pub task_profile: String,
    pub model: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub provider: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NoteProvenance {
    pub created_by: ProvenanceActor,
    pub capture_method: CaptureMethod,
    pub source_kind: SourceKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub origin_note_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub origin_ghost_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model_info: Option<ModelInfo>,
    pub created_at: String,
    pub raw_input_hash: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SourceExcerpt {
    pub source_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub excerpt_text: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub location_label: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_offset: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_offset: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NoteEmbed {
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source_id: Option<String>,
    pub url: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub canonical_url: Option<String>,
    pub title: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub publisher: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub excerpt: Option<String>,
    pub kind: SourceKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image_url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NoteEmbedInput {
    pub url: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub canonical_url: Option<String>,
    pub title: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub publisher: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub excerpt: Option<String>,
    pub kind: SourceKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image_url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ValuationRef {
    pub metric: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub multiple: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub unit: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub basis: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ResearchWorkspace {
    pub id: String,
    pub name: String,
    pub primary_ticker: String,
    pub scope: WorkspaceScope,
    pub stage: ResearchStage,
    pub default_view: WorkspaceViewKind,
    pub pinned_note_ids: Vec<String>,
    pub archived: bool,
    pub created_at: String,
    pub updated_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ResearchNote {
    pub id: String,
    pub workspace_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub company_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ticker: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source_id: Option<String>,
    pub raw_text: String,
    pub cleaned_text: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body_html: Option<String>,
    #[serde(default)]
    pub embeds: Vec<NoteEmbed>,
    #[serde(default)]
    pub file_ids: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    pub note_type: NoteType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub subtype: Option<String>,
    pub analyst_status: AnalystStatus,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ai_annotation: Option<String>,
    pub confidence: f32,
    pub evidence_status: EvidenceStatus,
    pub inferred_links: Vec<String>,
    pub ghost_status: GhostStatus,
    pub thesis_status: ThesisStatus,
    pub created_at: String,
    pub updated_at: String,
    pub provenance: NoteProvenance,
    pub tags: Vec<String>,
    pub catalysts: Vec<String>,
    pub risks: Vec<String>,
    pub valuation_refs: Vec<ValuationRef>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub time_horizon: Option<TimeHorizon>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scenario: Option<ScenarioKind>,
    pub priority: NotePriority,
    pub pinned: bool,
    pub archived: bool,
    pub revision: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source_excerpt: Option<SourceExcerpt>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_enriched_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_linked_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stale_reason: Option<StaleReason>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub superseded_by_note_id: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum LinkType {
    Supports,
    Contradicts,
    Qualifies,
    DerivedFrom,
    SourcedBy,
    Updates,
    Supersedes,
    PeerReadthrough,
    ValuationDependsOn,
    CatalystFor,
    RiskTo,
    TimeframeConflict,
    AssumptionFor,
    ManagementVsReality,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum LinkStrength {
    Weak,
    Medium,
    Strong,
    Critical,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceBasis {
    Lexical,
    SharedSource,
    Temporal,
    Numerical,
    Structured,
    ModelAssisted,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum LinkOrigin {
    Heuristic,
    Ai,
    Analyst,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NoteLink {
    pub id: String,
    pub workspace_id: String,
    pub from_note_id: String,
    pub to_note_id: String,
    pub link_type: LinkType,
    pub directional: bool,
    pub confidence: f32,
    pub strength: LinkStrength,
    pub evidence_basis: EvidenceBasis,
    pub created_by: LinkOrigin,
    pub created_at: String,
    pub updated_at: String,
    pub source_revision_pair: (u32, u32),
    pub stale: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stale_reason: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum GhostNoteClass {
    CandidateThesis,
    CandidateRisk,
    CandidateCatalyst,
    MissingEvidencePrompt,
    ContradictionAlert,
    ValuationBridge,
    ScenarioImplication,
    MemoOutlineSuggestion,
    ClaimSupportGap,
    ClaimPressureTest,
    ThesisDependencyGap,
    ContradictionResolution,
    EvidenceQualityWarning,
    StaleSupportWarning,
    NumericalClaimVerification,
    ThesisSynthesisCandidate,
    ModelAssumptionChallenge,
    ValuationSensitivityAlert,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum GhostTone {
    #[default]
    Tentative,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum GhostVisibilityState {
    Hidden,
    Collapsed,
    Visible,
    Pinned,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum GhostLifecycleState {
    Generated,
    Visible,
    Ignored,
    Dismissed,
    Accepted,
    Converted,
    Superseded,
    Stale,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum GhostResolutionStatus {
    #[default]
    Open,
    InProgress,
    Strengthened,
    Weakened,
    Waived,
    Blocked,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum GhostTargetClaimKind {
    #[default]
    SupportingPoint,
    CoreThesis,
    SubClaim,
    Risk,
    Catalyst,
    Assumption,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum GhostRecommendedAction {
    #[default]
    FindPrimarySource,
    LinkExistingNote,
    RewriteClaim,
    DowngradeToInference,
    MarkWaived,
    ResolveContradiction,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum GhostSupportStrength {
    #[default]
    None,
    Weak,
    Mixed,
    Strong,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum GhostFreshnessRisk {
    #[default]
    Low,
    Medium,
    High,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum MemoSectionKind {
    StockPitch,
    InvestmentMemo,
    BullCase,
    BearCase,
    CatalystCalendar,
    RiskRegister,
    ValuationWriteUp,
    EarningsPreview,
    EarningsRecap,
    WatchlistUpdate,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GhostNote {
    pub id: String,
    pub workspace_id: String,
    pub ghost_class: GhostNoteClass,
    pub headline: String,
    pub body: String,
    pub tone: GhostTone,
    pub confidence: f32,
    pub visibility_state: GhostVisibilityState,
    pub state: GhostLifecycleState,
    pub supporting_note_ids: Vec<String>,
    pub contradicting_note_ids: Vec<String>,
    pub source_ids: Vec<String>,
    pub evidence_threshold_met: bool,
    pub created_at: String,
    pub updated_at: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub superseded_by_ghost_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub promoted_note_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub memo_section_hint: Option<MemoSectionKind>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub target_note_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub parent_thesis_note_id: Option<String>,
    #[serde(default)]
    pub target_claim_text: String,
    #[serde(default)]
    pub target_claim_kind: GhostTargetClaimKind,
    #[serde(default)]
    pub why_it_matters: String,
    #[serde(default)]
    pub current_support_summary: String,
    #[serde(default)]
    pub counter_case: String,
    #[serde(default)]
    pub recommended_action: GhostRecommendedAction,
    #[serde(default)]
    pub recommended_action_rationale: String,
    #[serde(default)]
    pub resolution_status: GhostResolutionStatus,
    #[serde(default)]
    pub leverage_score: f32,
    #[serde(default)]
    pub support_strength: GhostSupportStrength,
    #[serde(default)]
    pub freshness_risk: GhostFreshnessRisk,
    #[serde(default)]
    pub agent_reasoning_version: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SourceRecord {
    pub id: String,
    pub workspace_id: String,
    pub kind: SourceKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ticker: Option<String>,
    pub title: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub publisher: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub canonical_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filing_accession: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub form_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub published_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub as_of_date: Option<String>,
    pub ingested_at: String,
    pub freshness_bucket: FreshnessBucket,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub checksum: Option<String>,
    pub metadata_json: Value,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub superseded_by_source_id: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ResearchFilePreviewKind {
    Image,
    Pdf,
    Text,
    None,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ResearchFile {
    pub id: String,
    pub workspace_id: String,
    pub original_name: String,
    pub stored_path: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mime_type: Option<String>,
    pub size_bytes: u64,
    pub checksum: String,
    pub preview_kind: ResearchFilePreviewKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extracted_text: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extracted_text_source: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extracted_text_truncated: Option<bool>,
    pub created_at: String,
    pub updated_at: String,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum JobKind {
    EnrichNote,
    InferLinks,
    EvaluateDuplicates,
    EvaluateGhosts,
    RefreshSourceMetadata,
    RecalculateStaleness,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum JobStatus {
    Queued,
    Running,
    Completed,
    Failed,
    Skipped,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PipelineJob {
    pub id: String,
    pub workspace_id: String,
    pub entity_id: String,
    pub job_kind: JobKind,
    pub status: JobStatus,
    pub attempt_count: u32,
    pub max_attempts: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub next_attempt_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_error: Option<String>,
    pub payload_json: Value,
    pub created_at: String,
    pub updated_at: String,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum AuditEntityKind {
    Workspace,
    Note,
    Link,
    Ghost,
    Source,
    Job,
    MemoExport,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum AuditActor {
    Analyst,
    System,
    Ai,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AuditEvent {
    pub id: String,
    pub workspace_id: String,
    pub entity_id: String,
    pub entity_kind: AuditEntityKind,
    pub action: String,
    pub actor: AuditActor,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub prior_revision: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub new_revision: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub job_id: Option<String>,
    pub source_ids: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub detail: Option<String>,
    pub created_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PlacementHint {
    pub tile_lane: String,
    pub kanban_column: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub graph_anchor_note_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NoteCaptureResult {
    pub note: ResearchNote,
    pub placement_hint: PlacementHint,
    pub queued_jobs: Vec<PipelineJob>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceProjection {
    pub workspace: ResearchWorkspace,
    pub active_view: WorkspaceViewKind,
    pub notes: Vec<ResearchNote>,
    pub links: Vec<NoteLink>,
    pub ghosts: Vec<GhostNote>,
    pub memo_blocks: Vec<MemoBlockCandidate>,
    pub graph_nodes: Vec<GraphNode>,
    pub graph_edges: Vec<GraphEdge>,
    pub kanban_columns: Vec<KanbanColumn>,
    pub timeline_events: Vec<TimelineEvent>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GraphNode {
    pub id: String,
    pub label: String,
    pub kind: String,
    pub confidence: f32,
    pub evidence_status: EvidenceStatus,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GraphEdge {
    pub id: String,
    pub from: String,
    pub to: String,
    pub link_type: LinkType,
    pub strength: LinkStrength,
    pub confidence: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct KanbanColumn {
    pub key: String,
    pub label: String,
    pub notes: Vec<ResearchNote>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TimelineEvent {
    pub id: String,
    pub label: String,
    pub note_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub at: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MemoBlockCandidate {
    pub section_kind: MemoSectionKind,
    pub headline: String,
    pub body: String,
    pub source_note_ids: Vec<String>,
    pub citation_refs: Vec<String>,
    pub confidence: f32,
    pub accepted: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ResearchBundleExport {
    pub workspace: ResearchWorkspace,
    pub notes: Vec<ResearchNote>,
    pub links: Vec<NoteLink>,
    pub ghosts: Vec<GhostNote>,
    pub sources: Vec<SourceRecord>,
    pub audit_events: Vec<AuditEvent>,
    pub markdown_memo: String,
    pub json_bundle: Value,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum ModelTemplateType {
    #[default]
    Blank,
    Dcf,
    ThreeStatement,
    ComparableCompanies,
    Scenario,
    HistoricalTrend,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
#[serde(rename_all = "snake_case")]
pub enum ModelEvidenceStatus {
    #[default]
    Unsourced,
    SingleSource,
    Corroborated,
    Inferred,
    Contradicted,
    Stale,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ModelCellAnnotation {
    pub cell_id: String,
    pub sheet_id: String,
    pub address: String,
    #[serde(default)]
    pub linked_note_ids: Vec<String>,
    pub evidence_status: ModelEvidenceStatus,
    pub confidence_score: f32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_evidence_update: Option<String>,
    pub updated_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FinancialModel {
    pub id: String,
    pub workspace_id: String,
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ticker: Option<String>,
    pub template_type: ModelTemplateType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scenario_group_id: Option<String>,
    pub workbook_json: Value,
    #[serde(default)]
    pub annotations: Vec<ModelCellAnnotation>,
    pub created_at: String,
    pub updated_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ModelDataBindingKind {
    SecEdgar,
    Mapping,
    Market,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ModelBindingRefreshPolicy {
    Manual,
    OnOpen,
    OnSourceUpdate,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ModelDataBinding {
    pub id: String,
    pub model_id: String,
    pub sheet_id: String,
    pub range: String,
    pub kind: ModelDataBindingKind,
    pub source_key: String,
    pub refresh_policy: ModelBindingRefreshPolicy,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_refresh_at: Option<String>,
    pub created_at: String,
    pub updated_at: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EvaluateFinancialModelRequest {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub workbook_json: Option<Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EvaluatedModelCell {
    pub sheet_id: String,
    pub address: String,
    pub value: Value,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ModelFormulaError {
    pub sheet_id: String,
    pub address: String,
    pub formula: String,
    pub message: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ModelDependencyEdge {
    pub from_sheet_id: String,
    pub from_address: String,
    pub to_sheet_id: String,
    pub to_address: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EvaluateFinancialModelResult {
    pub evaluated_cells: Vec<EvaluatedModelCell>,
    pub formula_errors: Vec<ModelFormulaError>,
    pub dependency_graph: Vec<ModelDependencyEdge>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NoteAuditTrail {
    pub note: ResearchNote,
    pub links: Vec<NoteLink>,
    pub related_ghosts: Vec<GhostNote>,
    pub sources: Vec<SourceRecord>,
    pub files: Vec<ResearchFile>,
    pub audit_events: Vec<AuditEvent>,
    pub memo_blocks: Vec<MemoBlockCandidate>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SourceReferenceInput {
    pub kind: SourceKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub published_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filing_accession: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub form_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub excerpt_text: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub location_label: Option<String>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PreviewLinkRequest {
    pub url: String,
    #[serde(default)]
    pub kind: Option<SourceKind>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PreviewLinkResult {
    pub embed: NoteEmbedInput,
    pub source_ref: SourceReferenceInput,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub suggested_title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub suggested_body_text: Option<String>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CreateResearchWorkspaceRequest {
    pub name: String,
    pub primary_ticker: String,
    #[serde(default)]
    pub stage: ResearchStage,
    #[serde(default)]
    pub default_view: WorkspaceViewKind,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CaptureResearchNoteRequest {
    pub workspace_id: String,
    pub raw_text: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body_html: Option<String>,
    #[serde(default)]
    pub embeds: Vec<NoteEmbedInput>,
    #[serde(default)]
    pub file_ids: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub company_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ticker: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source_ref: Option<SourceReferenceInput>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub position_hint: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_note_type_override: Option<NoteType>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UpdateResearchNoteRequest {
    pub note_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub raw_text: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body_html: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub embeds: Option<Vec<NoteEmbedInput>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub file_ids: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub note_type: Option<NoteType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub subtype: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub analyst_status: Option<AnalystStatus>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pinned: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub priority: Option<NotePriority>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thesis_status: Option<ThesisStatus>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ArchiveResearchNoteRequest {
    pub note_id: String,
    pub archived: bool,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ListResearchNotesRequest {
    pub workspace_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub include_archived: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub note_type: Option<NoteType>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GetNotesByTickerRequest {
    pub ticker: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<usize>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GetWorkspaceProjectionRequest {
    pub workspace_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub view: Option<WorkspaceViewKind>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ListFinancialModelsRequest {
    pub workspace_id: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GetFinancialModelRequest {
    pub model_id: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CreateFinancialModelRequest {
    pub workspace_id: String,
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ticker: Option<String>,
    #[serde(default)]
    pub template_type: ModelTemplateType,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SaveFinancialModelRequest {
    pub model_id: String,
    pub workbook_json: Value,
    #[serde(default)]
    pub annotations: Vec<ModelCellAnnotation>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ListModelDataBindingsRequest {
    pub model_id: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SaveModelDataBindingRequest {
    pub binding: ModelDataBinding,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RefreshModelDataBindingsRequest {
    pub model_id: String,
    #[serde(default)]
    pub binding_ids: Vec<String>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LinkNoteToModelCellRequest {
    pub model_id: String,
    pub sheet_id: String,
    pub address: String,
    pub note_id: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UnlinkNoteFromModelCellRequest {
    pub model_id: String,
    pub sheet_id: String,
    pub address: String,
    pub note_id: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UpdateModelCellAnnotationRequest {
    pub model_id: String,
    pub annotation: ModelCellAnnotation,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ListModelCellAnnotationsRequest {
    pub model_id: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ExportFinancialModelXlsxRequest {
    pub model_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ExportFinancialModelXlsxResult {
    pub file_path: String,
    pub model_id: String,
    pub sheet_count: usize,
    pub exported_at: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ListNoteLinksRequest {
    pub workspace_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub note_id: Option<String>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ListWorkspaceGhostNotesRequest {
    pub workspace_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub include_hidden: Option<bool>,
}

#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum GhostReviewAction {
    Accept,
    Ignore,
    Dismiss,
    Pin,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ReviewGhostNoteRequest {
    pub ghost_note_id: String,
    pub action: GhostReviewAction,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ResolveGhostInterventionRequest {
    pub ghost_note_id: String,
    pub workspace_id: String,
    pub resolution_status: GhostResolutionStatus,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub action_taken: Option<GhostRecommendedAction>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub notes: Option<String>,
    #[serde(default)]
    pub linked_note_ids: Vec<String>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PromoteNoteToThesisRequest {
    pub note_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thesis_status: Option<ThesisStatus>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub note_type: Option<NoteType>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RetryResearchJobsRequest {
    pub workspace_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub job_kind: Option<JobKind>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ListResearchJobsRequest {
    pub workspace_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub job_kind: Option<JobKind>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GetNoteAuditTrailRequest {
    pub note_id: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UploadResearchFileInput {
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mime_type: Option<String>,
    pub bytes: Vec<u8>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extracted_text: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extracted_text_source: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extracted_text_truncated: Option<bool>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UploadResearchFilesRequest {
    pub workspace_id: String,
    pub files: Vec<UploadResearchFileInput>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ListResearchFilesRequest {
    pub workspace_id: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GetResearchFileTextPreviewRequest {
    pub file_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_bytes: Option<usize>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ResearchFileTextPreview {
    pub file_id: String,
    pub text: String,
    pub truncated: bool,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ExportResearchBundleRequest {
    pub workspace_id: String,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SearchResearchNotesRequest {
    pub workspace_id: String,
    pub query: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub include_archived: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub note_type: Option<NoteType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<usize>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UpdateResearchWorkspaceRequest {
    pub workspace_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stage: Option<ResearchStage>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default_view: Option<WorkspaceViewKind>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pinned_note_ids: Option<Vec<String>>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ArchiveResearchWorkspaceRequest {
    pub workspace_id: String,
    pub archived: bool,
}
