use crate::tools::{ToolCall, ToolResponse};
use md_stream::{MdElement, Partial, StreamParser};

/// Raw image bytes with MIME type, attached to a user message.
/// `id` is a stable hash used as the egui image cache key.
/// Bytes are stored in an `Arc` so cloning for egui rendering is a refcount
/// bump rather than a full heap copy.
#[derive(Debug, Clone)]
pub struct ImageAttachment {
    pub id: u64,
    pub bytes: std::sync::Arc<[u8]>,
    pub mime_type: String,
}

impl ImageAttachment {
    pub fn new(bytes: Vec<u8>, mime_type: impl Into<String>) -> Self {
        use std::collections::hash_map::DefaultHasher;
        use std::hash::{Hash, Hasher};
        let mut h = DefaultHasher::new();
        bytes.hash(&mut h);
        Self {
            id: h.finish(),
            bytes: bytes.into(),
            mime_type: mime_type.into(),
        }
    }

    /// Stable URI for egui_extras image loader — decoded once, cached by this key.
    pub fn egui_uri(&self) -> String {
        format!("bytes://img_attach_{}", self.id)
    }
}

/// A user message: text plus optional image attachments.
#[derive(Debug, Clone, Default)]
pub struct UserMessage {
    pub text: String,
    pub images: Vec<ImageAttachment>,
}

impl UserMessage {
    pub fn new(text: impl Into<String>, images: Vec<ImageAttachment>) -> Self {
        Self {
            text: text.into(),
            images,
        }
    }

    pub fn as_str(&self) -> &str {
        &self.text
    }
}

impl From<String> for UserMessage {
    fn from(s: String) -> Self {
        Self {
            text: s,
            images: vec![],
        }
    }
}

impl From<&str> for UserMessage {
    fn from(s: &str) -> Self {
        Self {
            text: s.to_owned(),
            images: vec![],
        }
    }
}

/// Pre-parsed markdown with source text for span resolution.
#[derive(Debug, Clone)]
pub struct ParsedMarkdown {
    pub source: String,
    pub elements: Vec<MdElement>,
}

impl ParsedMarkdown {
    /// Parse a markdown string into elements.
    pub fn parse(text: &str) -> Self {
        let mut parser = StreamParser::new();
        parser.push(text);
        parser.finalize();
        let (elements, source) = parser.into_parts();
        Self { source, elements }
    }
}
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tokio::sync::oneshot;
use uuid::Uuid;

/// A selectable option in a shared question-set prompt.
#[derive(Debug, Clone, Deserialize)]
pub struct QuestionOption {
    pub label: String,
    pub description: String,
}

/// A single question in a shared question-set prompt.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserQuestion {
    pub question: String,
    pub header: String,
    #[serde(default)]
    pub multi_select: bool,
    pub options: Vec<QuestionOption>,
}

/// Parsed multi-question prompt input for the shared permission UI.
#[derive(Debug, Clone, Deserialize)]
pub struct QuestionSetInput {
    pub questions: Vec<UserQuestion>,
}

/// Structured approval prompt payload that can be rendered with the generic
/// allow/deny permission UI.
#[derive(Debug, Clone, Deserialize)]
pub struct ApprovalPromptInput {
    pub questions: Vec<ApprovalPromptQuestion>,
}

/// One approval question shown in the compact permission card.
#[derive(Debug, Clone, Deserialize)]
pub struct ApprovalPromptQuestion {
    #[serde(default)]
    pub header: Option<String>,
    #[serde(default)]
    pub question: Option<String>,
}

/// Backend-neutral view model for permission requests.
#[derive(Debug, Clone)]
pub enum PermissionView {
    /// A simple approval prompt rendered with allow/deny controls.
    Approval(ApprovalPromptInput),
    /// A structured question set rendered with the shared question UI.
    QuestionSet(QuestionSetInput),
    /// Exit plan mode review card.
    PlanReview(Option<ParsedMarkdown>),
    /// Fallback when we only know how to show raw tool input.
    RawFallback,
}

impl PermissionView {
    /// Infer the most specific UI view we can safely render from raw request data.
    pub fn infer(tool_name: &str, tool_input: &Value) -> Self {
        if tool_name == "ExitPlanMode" {
            return Self::PlanReview(
                tool_input
                    .get("plan")
                    .and_then(|v| v.as_str())
                    .map(ParsedMarkdown::parse),
            );
        }

        if tool_name == "AskUserQuestion" {
            if let Ok(questions) = serde_json::from_value::<QuestionSetInput>(tool_input.clone()) {
                if !questions.questions.is_empty() {
                    return Self::QuestionSet(questions);
                }
            }
        }

        if let Some(prompt) = Self::infer_approval_prompt(tool_input) {
            return Self::Approval(prompt);
        }

        Self::RawFallback
    }

    /// Infer an approval prompt only from the narrow shape we intentionally
    /// emit for compact allow/deny cards.
    fn infer_approval_prompt(tool_input: &Value) -> Option<ApprovalPromptInput> {
        let questions = tool_input.get("questions")?.as_array()?;
        if questions.is_empty() {
            return None;
        }

        let mut parsed_questions = Vec::with_capacity(questions.len());
        for question in questions {
            let obj = question.as_object()?;
            if obj.keys().any(|key| key != "header" && key != "question") {
                return None;
            }

            let header = obj
                .get("header")
                .and_then(|v| v.as_str())
                .map(ToOwned::to_owned);
            let prompt = obj
                .get("question")
                .and_then(|v| v.as_str())
                .map(ToOwned::to_owned);

            let has_display_text = header.as_deref().is_some_and(|text| !text.is_empty())
                || prompt.as_deref().is_some_and(|text| !text.is_empty());
            if !has_display_text {
                return None;
            }

            parsed_questions.push(ApprovalPromptQuestion {
                header,
                question: prompt,
            });
        }

        Some(ApprovalPromptInput {
            questions: parsed_questions,
        })
    }

    /// Return the shared question set when this view supports structured answers.
    pub fn question_set(&self) -> Option<&QuestionSetInput> {
        match self {
            Self::QuestionSet(questions) => Some(questions),
            _ => None,
        }
    }

    /// Return the structured approval prompt when this view can use the compact card.
    pub fn approval_prompt(&self) -> Option<&ApprovalPromptInput> {
        match self {
            Self::Approval(prompt) => Some(prompt),
            _ => None,
        }
    }

    /// Whether this request should use the plan review renderer.
    pub fn is_plan_review(&self) -> bool {
        matches!(self, Self::PlanReview(_))
    }

    /// Return parsed markdown for a plan review if it is available.
    pub fn plan_markdown(&self) -> Option<&ParsedMarkdown> {
        match self {
            Self::PlanReview(Some(plan)) => Some(plan),
            _ => None,
        }
    }
}

/// User's answer to a question
#[derive(Debug, Clone, Default, Serialize)]
pub struct QuestionAnswer {
    /// Selected option indices
    pub selected: Vec<usize>,
    /// Custom "Other" text if provided
    pub other_text: Option<String>,
}

/// Render question-set answers as plain, human-readable prose — one
/// `Header: label, label, other` line per question.
///
/// The result is injected to the model verbatim as user text, so it MUST be
/// prose, NOT a JSON blob. Both answer paths share this: the engine's remote
/// [`respond_question`](crate::Engine::respond_question) (carrying it in the
/// permission_response `message`) and desktop dave's local answer handling.
/// Formatting happens at answer time because only the sender holds the
/// question metadata needed to resolve selected indices to option labels.
pub fn format_question_answers(
    questions: Option<&QuestionSetInput>,
    answers: &[QuestionAnswer],
) -> String {
    let questions = questions.map(|q| q.questions.as_slice()).unwrap_or(&[]);

    answers
        .iter()
        .enumerate()
        .map(|(q_idx, answer)| {
            let question = questions.get(q_idx);

            // Resolve selected indices to option labels when we have the
            // question metadata; otherwise fall back to the raw index. Any
            // free-text "other" is appended as its own part.
            let mut parts: Vec<String> = answer
                .selected
                .iter()
                .map(|&idx| match question.and_then(|q| q.options.get(idx)) {
                    Some(opt) => opt.label.clone(),
                    None => idx.to_string(),
                })
                .collect();
            if let Some(other) = answer.other_text.as_ref().filter(|other| !other.is_empty()) {
                parts.push(other.clone());
            }

            let label = match question {
                Some(q) if !q.header.is_empty() => q.header.clone(),
                Some(q) if !q.question.is_empty() => q.question.clone(),
                _ => format!("Question {}", q_idx + 1),
            };

            format!("{label}: {}", parts.join(", "))
        })
        .collect::<Vec<_>>()
        .join("\n")
}

/// A request for user permission to use a tool (displayable data only)
#[derive(Debug, Clone)]
pub struct PermissionRequest {
    /// Unique identifier for this permission request
    pub id: Uuid,
    /// The tool that wants to be used
    pub tool_name: String,
    /// The arguments the tool will be called with
    pub tool_input: serde_json::Value,
    /// Backend-neutral UI model used to render this request consistently.
    pub view: PermissionView,
    /// The user's response (None if still pending)
    pub response: Option<PermissionResponseType>,
    /// For question-set prompts: pre-computed summary of answers for display
    pub answer_summary: Option<AnswerSummary>,
    /// Whether this request was auto-accepted (runtime allowlist / Auto Accept
    /// All) rather than approved by an explicit user click. The user never got
    /// to review it up front, so its responded row starts expanded for
    /// after-the-fact inspection. Defaults to `false` (manual approval).
    pub auto_accepted: bool,
}

impl PermissionRequest {
    /// Build a new permission request, inferring a shared UI view when one is
    /// not provided explicitly by the backend.
    pub fn new(
        id: Uuid,
        tool_name: String,
        tool_input: Value,
        view: Option<PermissionView>,
        response: Option<PermissionResponseType>,
        answer_summary: Option<AnswerSummary>,
    ) -> Self {
        let view = view.unwrap_or_else(|| PermissionView::infer(&tool_name, &tool_input));

        Self {
            id,
            tool_name,
            tool_input,
            view,
            response,
            answer_summary,
            auto_accepted: false,
        }
    }

    /// A fresh, still-pending request: no response yet, no answer summary, and
    /// the UI view inferred from the tool. This is the common case; use
    /// [`new`](Self::new) when a backend supplies an explicit view or a
    /// pre-set response.
    pub fn pending(id: Uuid, tool_name: String, tool_input: Value) -> Self {
        Self::new(id, tool_name, tool_input, None, None, None)
    }

    /// Mark a request as auto-accepted by the runtime allowlist / Auto Accept
    /// All: records the `Allowed` response and flags it so the responded row
    /// starts expanded for after-the-fact review (the user never approved it
    /// up front). These two always travel together for auto-acceptance.
    pub fn auto_accept(mut self) -> Self {
        self.response = Some(PermissionResponseType::Allowed);
        self.auto_accepted = true;
        self
    }
}

/// A single entry in an answer summary
#[derive(Debug, Clone)]
pub struct AnswerSummaryEntry {
    /// The question header (e.g., "Library", "Approach")
    pub header: String,
    /// The selected answer text, comma-separated if multiple
    pub answer: String,
}

/// Pre-computed summary of a question-set response for display
#[derive(Debug, Clone)]
pub struct AnswerSummary {
    pub entries: Vec<AnswerSummaryEntry>,
}

/// A permission request with the response channel (for channel communication)
pub struct PendingPermission {
    /// The displayable request data
    pub request: PermissionRequest,
    /// Channel to send the user's response back
    pub response_tx: oneshot::Sender<PermissionResponse>,
}

/// The user's response to a permission request
#[derive(Debug, Clone)]
pub enum PermissionResponse {
    /// Allow the tool to execute, with an optional message for the AI
    Allow { message: Option<String> },
    /// Deny the tool execution with a reason
    Deny { reason: String },
    /// Cancel the current turn after marking the request denied in the UI
    Cancel { reason: String },
}

impl PermissionResponse {
    /// Whether this response should cancel the current turn after the tool is denied.
    pub fn cancels_turn(&self) -> bool {
        matches!(self, Self::Cancel { .. })
    }
}

/// The recorded response type for display purposes (without channel details)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionResponseType {
    Allowed,
    Denied,
}

/// Metadata about a completed tool execution from an agentic backend.
/// Used as a variant in `ToolResponses` to unify with other tool responses.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutedTool {
    pub tool_name: String,
    pub summary: String, // e.g., "154 lines", "exit 0", "3 matches"
    /// Which subagent (Task tool_use_id) produced this result, if any
    pub parent_task_id: Option<String>,
    /// Pre-computed file update for diff rendering (not serialized)
    #[serde(skip)]
    pub file_update: Option<crate::file_update::FileUpdate>,
}

/// Session initialization info from Claude Code CLI
#[derive(Debug, Clone, Default)]
pub struct SessionInfo {
    /// Available tools in this session
    pub tools: Vec<String>,
    /// Model being used (e.g., "claude-opus-4-5-20251101")
    pub model: Option<String>,
    /// Permission mode (e.g., "default", "plan")
    pub permission_mode: Option<String>,
    /// Available slash commands
    pub slash_commands: Vec<String>,
    /// Available agent types for Task tool
    pub agents: Vec<String>,
    /// Claude Code CLI version
    pub cli_version: Option<String>,
    /// Current working directory
    pub cwd: Option<String>,
    /// Session ID from Claude Code
    pub claude_session_id: Option<String>,
}

/// Status of a subagent spawned by the Task tool
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubagentStatus {
    /// Subagent is running
    Running,
    /// Subagent completed successfully
    Completed,
    /// Subagent failed with an error
    Failed,
}

/// Information about a subagent spawned by the Task tool
#[derive(Debug, Clone)]
pub struct SubagentInfo {
    /// Unique ID for this subagent task
    pub task_id: String,
    /// Description of what the subagent is doing
    pub description: String,
    /// Type of subagent (e.g., "Explore", "Plan", "Bash")
    pub subagent_type: String,
    /// Current status
    pub status: SubagentStatus,
    /// Output content (truncated for display)
    pub output: String,
    /// Maximum output size to keep (for size-restricted window)
    pub max_output_size: usize,
    /// Tool results produced by this subagent
    pub tool_results: Vec<ExecutedTool>,
    /// Whether this subagent runs in the background (`run_in_background`).
    ///
    /// A background subagent's lifecycle is driven by the CLI's
    /// `task_started` / `task_notification` system messages: it keeps running
    /// after the launching turn's `Result` and completes on a spontaneous
    /// wake-up turn, not on its launch tool result. The UI renders it as
    /// "running in background" until the wake-up lands.
    pub background: bool,
}

/// An assistant message with incremental markdown parsing support.
///
/// During streaming, tokens are pushed to the parser incrementally.
/// After finalization (stream end), parsed elements are cached.
pub struct AssistantMessage {
    /// Raw accumulated text (kept for API serialization)
    text: String,
    /// Incremental parser for this message (None after finalization)
    parser: Option<StreamParser>,
    /// Cached parsed elements (populated after finalization)
    cached_elements: Option<Vec<MdElement>>,
}

impl std::fmt::Debug for AssistantMessage {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AssistantMessage")
            .field("text", &self.text)
            .field("is_streaming", &self.parser.is_some())
            .field(
                "cached_elements",
                &self.cached_elements.as_ref().map(|e| e.len()),
            )
            .finish()
    }
}

impl Clone for AssistantMessage {
    fn clone(&self) -> Self {
        // StreamParser doesn't implement Clone, so we need special handling.
        // For cloned messages (which are typically finalized), we just clone
        // the text and cached elements. If there's an active parser, we
        // re-parse from the raw text.
        if let Some(cached) = &self.cached_elements {
            Self {
                text: self.text.clone(),
                parser: None,
                cached_elements: Some(cached.clone()),
            }
        } else {
            // Active streaming - re-parse from text
            let mut parser = StreamParser::new();
            parser.push(&self.text);
            Self {
                text: self.text.clone(),
                parser: Some(parser),
                cached_elements: None,
            }
        }
    }
}

impl AssistantMessage {
    /// Create a new assistant message with a fresh parser.
    pub fn new() -> Self {
        Self {
            text: String::new(),
            parser: Some(StreamParser::new()),
            cached_elements: None,
        }
    }

    /// Create from existing text (e.g., when loading from storage).
    pub fn from_text(text: String) -> Self {
        let mut parser = StreamParser::new();
        parser.push(&text);
        parser.finalize();
        let cached = parser.parsed().to_vec();
        Self {
            text,
            parser: None,
            cached_elements: Some(cached),
        }
    }

    /// Push a new token and update the parser.
    pub fn push_token(&mut self, token: &str) {
        self.text.push_str(token);
        if let Some(parser) = &mut self.parser {
            parser.push(token);
        }
    }

    /// Finalize the message (call when stream ends).
    /// This caches the parsed elements and drops the parser.
    pub fn finalize(&mut self) {
        if let Some(mut parser) = self.parser.take() {
            parser.finalize();
            self.cached_elements = Some(parser.parsed().to_vec());
        }
    }

    /// Get the raw text content.
    pub fn text(&self) -> &str {
        &self.text
    }

    /// Get the buffer for resolving spans in parsed elements.
    /// This is the same as text() — both the parser and AssistantMessage
    /// maintain identical buffers via push_str(token).
    pub fn buffer(&self) -> &str {
        &self.text
    }

    /// Get parsed markdown elements.
    pub fn parsed_elements(&self) -> &[MdElement] {
        if let Some(cached) = &self.cached_elements {
            cached
        } else if let Some(parser) = &self.parser {
            parser.parsed()
        } else {
            &[]
        }
    }

    /// Get the current partial (in-progress) element, if any.
    pub fn partial(&self) -> Option<&Partial> {
        self.parser.as_ref().and_then(|p| p.partial())
    }

    /// Check if the message is still being streamed.
    pub fn is_streaming(&self) -> bool {
        self.parser.is_some()
    }
}

impl Default for AssistantMessage {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, Clone)]
pub enum Message {
    System(String),
    Error(String),
    User(UserMessage),
    Assistant(AssistantMessage),
    ToolCalls(Vec<ToolCall>),
    ToolResponse(ToolResponse),
    /// A permission request from the AI that needs user response
    PermissionRequest(PermissionRequest),
    /// Conversation was compacted
    CompactionComplete(CompactionInfo),
    /// A subagent spawned by Task tool
    Subagent(SubagentInfo),
    /// TodoWrite tool input for task list display
    TodoUpdate(serde_json::Value),
}

/// Compaction info from compact_boundary system message
#[derive(Debug, Clone)]
pub struct CompactionInfo {
    /// Number of tokens before compaction
    pub pre_tokens: u64,
}

/// Usage metrics from a query's usage object (per-turn or cumulative)
#[derive(Debug, Clone, Default)]
pub struct UsageInfo {
    pub input_tokens: u64,
    pub cache_creation_input_tokens: u64,
    pub cache_read_input_tokens: u64,
    pub output_tokens: u64,
    pub cost_usd: Option<f64>,
    pub num_turns: u32,
}

impl UsageInfo {
    /// Total tokens occupying the context window for this request.
    /// Includes uncached tokens, cache-creation tokens, and cache-read tokens —
    /// all three contribute to context window consumption.
    pub fn context_tokens(&self) -> u64 {
        self.input_tokens + self.cache_creation_input_tokens + self.cache_read_input_tokens
    }
}

/// Get context window size for a model name.
/// All current Claude models have 200K context.
pub fn context_window_for_model(_model: Option<&str>) -> u64 {
    200_000
}

/// The ai backends response. Since we are using streaming APIs these are
/// represented as individual tokens or tool calls
pub enum DaveApiResponse {
    ToolCalls(Vec<ToolCall>),
    Token(String),
    Failed(String),
    /// A permission request that needs to be displayed to the user
    PermissionRequest(PendingPermission),
    /// Metadata from a completed tool execution
    ToolResult(ExecutedTool),
    /// Session initialization info from Claude Code CLI
    SessionInfo(SessionInfo),
    /// Subagent spawned by Task tool
    SubagentSpawned(SubagentInfo),
    /// Subagent output update
    SubagentOutput {
        task_id: String,
        output: String,
    },
    /// Subagent completed
    SubagentCompleted {
        task_id: String,
        result: String,
    },
    /// Subagent failed (e.g. a background subagent whose `task_notification`
    /// reported a non-completed status). `task_id` is the originating tool_use
    /// id, matching the entry created on spawn.
    SubagentFailed {
        task_id: String,
        error: String,
    },
    /// Conversation compaction started
    CompactionStarted,
    /// Conversation compaction completed with token info
    CompactionComplete(CompactionInfo),
    /// Per-turn usage update from an AssistantMessage (accurate context window snapshot)
    UsageUpdate(UsageInfo),
    /// Query completed with usage metrics (cumulative totals, cost, and turn count)
    QueryComplete(UsageInfo),
    /// TodoWrite tool was called with these todos
    TodoUpdate(serde_json::Value),
}

impl Message {
    pub fn tool_error(id: String, msg: String) -> Self {
        Self::ToolResponse(ToolResponse::error(id, msg))
    }
}

#[cfg(test)]
mod tests {
    use super::{
        PermissionRequest, PermissionResponseType, PermissionView, QuestionSetInput, UserQuestion,
    };
    use serde_json::json;
    use uuid::Uuid;

    #[test]
    fn permission_view_infers_compact_approval_prompt_shape() {
        let tool_input = json!({
            "questions": [{
                "header": "Approve app tool call?",
                "question": "Allow this action?"
            }]
        });

        let view = PermissionView::infer("SaveIssue", &tool_input);

        match view {
            PermissionView::Approval(prompt) => {
                assert_eq!(prompt.questions.len(), 1);
                assert_eq!(
                    prompt.questions[0].header.as_deref(),
                    Some("Approve app tool call?")
                );
                assert_eq!(
                    prompt.questions[0].question.as_deref(),
                    Some("Allow this action?")
                );
            }
            other => panic!("expected Approval view, got {:?}", other),
        }
    }

    #[test]
    fn permission_request_new_infers_question_set_for_ask_user_question() {
        let request = PermissionRequest::new(
            Uuid::new_v4(),
            "AskUserQuestion".to_string(),
            json!({
                "questions": [{
                    "header": "Theme",
                    "question": "Pick a theme",
                    "multiSelect": false,
                    "options": [{
                        "label": "Light",
                        "description": "Bright background"
                    }]
                }]
            }),
            None,
            Some(PermissionResponseType::Denied),
            None,
        );

        match &request.view {
            PermissionView::QuestionSet(QuestionSetInput { questions }) => {
                assert_eq!(questions.len(), 1);
                let UserQuestion {
                    header,
                    question,
                    multi_select,
                    options,
                } = &questions[0];
                assert_eq!(header, "Theme");
                assert_eq!(question, "Pick a theme");
                assert!(!multi_select);
                assert_eq!(options.len(), 1);
                assert_eq!(options[0].label, "Light");
                assert_eq!(options[0].description, "Bright background");
            }
            other => panic!("expected QuestionSet view, got {:?}", other),
        }

        assert_eq!(request.response, Some(PermissionResponseType::Denied));
    }

    #[test]
    fn permission_view_empty_question_set_falls_back_to_raw() {
        let view = PermissionView::infer("AskUserQuestion", &json!({ "questions": [] }));
        assert!(matches!(view, PermissionView::RawFallback));
    }
}
