/// Remove `<think>...</think>` blocks (Chain-of-Thought) from text.
///
/// Handles Claude extended thinking, DeepSeek CoT, and similar patterns.
/// Multiple think blocks are removed. Nested tags are not expected but
/// handled by greedy matching to the last `</think>`.
pub fn strip_think_blocks(text: &str) -> std::borrow::Cow<'_, str> {
    // Fast path: no think tag at all
    if !text.contains("<think>") {
        return std::borrow::Cow::Borrowed(text);
    }

    let mut result = String::with_capacity(text.len());
    let mut remaining = text;

    while let Some(open) = remaining.find("<think>") {
        result.push_str(&remaining[..open]);
        let after_open = &remaining[open + 7..]; // len("<think>") == 7
        if let Some(close) = after_open.find("</think>") {
            remaining = &after_open[close + 8..]; // len("</think>") == 8
        } else {
            // Unclosed <think> — strip everything after it
            remaining = "";
            break;
        }
    }
    result.push_str(remaining);

    let trimmed = result.trim();
    if trimmed.len() == text.trim().len() && trimmed == text.trim() {
        // Nothing was actually removed (shouldn't happen given the contains check, but safe)
        return std::borrow::Cow::Borrowed(text);
    }

    std::borrow::Cow::Owned(result)
}

/// Remove markdown code fences and return inner content.
///
/// Handles:
/// - `` ```json\n...\n``` ``
/// - `` ```\n...\n``` ``
/// - Multiple code blocks (returns content of the first one)
///
/// If no code fence is found, returns the input unchanged.
pub fn strip_fences(text: &str) -> &str {
    let trimmed = text.trim();

    // Find opening fence: ``` optionally followed by a language tag
    let Some(open_start) = find_fence_open(trimmed) else {
        return text;
    };

    // Find the end of the opening fence line
    let after_open = &trimmed[open_start..];
    let Some(newline_pos) = after_open.find('\n') else {
        return text;
    };
    let content_start = open_start + newline_pos + 1;

    // Find closing fence: ``` at the start of a line
    let content_region = &trimmed[content_start..];
    let Some(close_offset) = find_fence_close(content_region) else {
        // No closing fence — return everything after opening fence
        return content_region.trim();
    };

    trimmed[content_start..content_start + close_offset].trim()
}

/// Find the byte offset of an opening fence (``` at line start).
fn find_fence_open(text: &str) -> Option<usize> {
    find_triple_backtick_line_start(text)
}

/// Find ``` that appears at the start of a line.
fn find_triple_backtick_line_start(text: &str) -> Option<usize> {
    let bytes = text.as_bytes();
    let len = bytes.len();
    if len < 3 {
        return None;
    }

    // Check position 0
    if bytes[0] == b'`' && bytes[1] == b'`' && bytes[2] == b'`' {
        return Some(0);
    }

    // Check after each newline
    let mut i = 0;
    while i < len {
        if bytes[i] == b'\n' {
            let start = i + 1;
            // Skip leading whitespace
            let mut j = start;
            while j < len && (bytes[j] == b' ' || bytes[j] == b'\t') {
                j += 1;
            }
            if j + 2 < len && bytes[j] == b'`' && bytes[j + 1] == b'`' && bytes[j + 2] == b'`' {
                return Some(j);
            }
        }
        i += 1;
    }

    None
}

/// Find closing ``` in content region (must be at start of a line).
fn find_fence_close(text: &str) -> Option<usize> {
    let bytes = text.as_bytes();
    let len = bytes.len();

    // Check position 0 (content starts right at a closing fence)
    if len >= 3 && bytes[0] == b'`' && bytes[1] == b'`' && bytes[2] == b'`' {
        return Some(0);
    }

    let mut i = 0;
    while i < len {
        if bytes[i] == b'\n' {
            let after_nl = i + 1;
            // Skip leading whitespace
            let mut j = after_nl;
            while j < len && (bytes[j] == b' ' || bytes[j] == b'\t') {
                j += 1;
            }
            if j + 2 < len && bytes[j] == b'`' && bytes[j + 1] == b'`' && bytes[j + 2] == b'`' {
                // Return offset up to (but not including) the newline before closing fence
                return Some(i);
            }
            // Also handle case where ``` is at end of text (j+2 == len-1)
            if j + 2 == len - 1 && bytes[j] == b'`' && bytes[j + 1] == b'`' && bytes[j + 2] == b'`'
            {
                return Some(i);
            }
        }
        i += 1;
    }

    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fenced_json() {
        let input = "```json\n{\"a\": 1}\n```";
        assert_eq!(strip_fences(input), "{\"a\": 1}");
    }

    #[test]
    fn fenced_no_lang() {
        let input = "```\n{\"a\": 1}\n```";
        assert_eq!(strip_fences(input), "{\"a\": 1}");
    }

    #[test]
    fn no_fence() {
        let input = "{\"a\": 1}";
        assert_eq!(strip_fences(input), "{\"a\": 1}");
    }

    #[test]
    fn fence_with_surrounding_text() {
        let input = "Here's the result:\n```json\n{\"a\": 1}\n```\nHope that helps!";
        assert_eq!(strip_fences(input), "{\"a\": 1}");
    }

    #[test]
    fn unclosed_fence() {
        let input = "```json\n{\"a\": 1}";
        assert_eq!(strip_fences(input), "{\"a\": 1}");
    }

    #[test]
    fn multiline_content() {
        let input = "```json\n{\n  \"a\": 1,\n  \"b\": 2\n}\n```";
        assert_eq!(strip_fences(input), "{\n  \"a\": 1,\n  \"b\": 2\n}");
    }

    #[test]
    fn fence_with_indentation() {
        let input = "  ```json\n{\"a\": 1}\n  ```";
        assert_eq!(strip_fences(input), "{\"a\": 1}");
    }

    #[test]
    fn plain_text_no_fence() {
        let input = "No JSON here, just text.";
        assert_eq!(strip_fences(input), input);
    }

    #[test]
    fn empty_input() {
        assert_eq!(strip_fences(""), "");
    }

    // strip_think_blocks tests

    #[test]
    fn think_block_removed() {
        let input = "<think>Let me reason about this...</think>{\"a\": 1}";
        let result = strip_think_blocks(input);
        assert_eq!(result.trim(), "{\"a\": 1}");
    }

    #[test]
    fn multiple_think_blocks() {
        let input = "<think>first</think>Hello <think>second</think>{\"a\": 1}";
        let result = strip_think_blocks(input);
        assert_eq!(result.trim(), "Hello {\"a\": 1}");
    }

    #[test]
    fn no_think_block() {
        let input = "{\"a\": 1}";
        let result = strip_think_blocks(input);
        assert!(matches!(result, std::borrow::Cow::Borrowed(_)));
        assert_eq!(&*result, input);
    }

    #[test]
    fn unclosed_think_block() {
        let input = "<think>reasoning forever...";
        let result = strip_think_blocks(input);
        assert_eq!(result.trim(), "");
    }

    #[test]
    fn think_block_with_fence_and_json() {
        let input = "<think>\nI need to return JSON.\n</think>\n```json\n{\"status\": \"ok\"}\n```";
        let result = strip_think_blocks(input);
        assert!(result.contains("```json"));
        assert!(result.contains("\"status\""));
        assert!(!result.contains("<think>"));
    }
}
