// Copyright 2025 Lablup Inc. and Jeongkyu Shin
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crossterm::{
    cursor, queue,
    style::Print,
    terminal::{size, ClearType},
};
use std::io::{stdout, Write};

pub struct BufferWriter {
    buffer: String,
    line_count: usize,
}

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

impl BufferWriter {
    pub fn new() -> Self {
        Self {
            // Pre-allocate 64KB - sufficient for typical terminal content
            // while avoiding excessive memory usage
            buffer: String::with_capacity(64 * 1024),
            line_count: 0,
        }
    }

    pub fn get_buffer(&self) -> &str {
        &self.buffer
    }

    pub fn line_count(&self) -> usize {
        self.line_count
    }
}

impl Write for BufferWriter {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        let s = std::str::from_utf8(buf)
            .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid UTF-8"))?;

        // Count newlines in the new content
        self.line_count += s.matches('\n').count();

        self.buffer.push_str(s);
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

/// Differential renderer that only updates changed lines to eliminate flickering
pub struct DifferentialRenderer {
    previous_lines: Vec<String>,
    screen_height: usize,
    screen_width: usize,
    /// Hash of previous content for fast unchanged detection
    previous_content_hash: u64,
}

impl DifferentialRenderer {
    pub fn new() -> std::io::Result<Self> {
        let (width, height) = size().unwrap_or((80, 24));
        Ok(Self {
            previous_lines: Vec::new(),
            screen_height: height as usize,
            screen_width: width as usize,
            previous_content_hash: 0,
        })
    }

    /// Fast hash function for content comparison (FNV-1a)
    fn hash_content(content: &str) -> u64 {
        const FNV_OFFSET: u64 = 14695981039346656037;
        const FNV_PRIME: u64 = 1099511628211;

        let mut hash = FNV_OFFSET;
        for byte in content.bytes() {
            hash ^= byte as u64;
            hash = hash.wrapping_mul(FNV_PRIME);
        }
        hash
    }

    /// Render content with differential updates - only changed lines are updated
    pub fn render_differential(&mut self, content: &str) -> std::io::Result<()> {
        // Fast path: check if content is identical using hash
        let content_hash = Self::hash_content(content);
        if content_hash == self.previous_content_hash && !self.previous_lines.is_empty() {
            // Content is unchanged, skip all rendering work
            return Ok(());
        }

        // Content has changed, update hash
        self.previous_content_hash = content_hash;

        // Adjust buffer size if screen dimensions changed
        let (width, height) = size().unwrap_or((80, 24));
        if width as usize != self.screen_width || height as usize != self.screen_height {
            self.screen_width = width as usize;
            self.screen_height = height as usize;
            self.previous_lines
                .resize(self.screen_height, String::new());
        }

        // Initialize previous_lines on first run
        if self.previous_lines.is_empty() {
            self.previous_lines = vec![String::new(); self.screen_height];
        }

        let mut stdout = stdout();
        let mut current_line_count = 0;

        // Process lines directly from iterator, updating previous_lines in-place
        for (line_num, current_line) in content.lines().enumerate() {
            if line_num >= self.screen_height {
                break;
            }
            current_line_count = line_num + 1;

            // Check if this line has changed
            if self.previous_lines[line_num] != current_line {
                // Update this line - clear it first to prevent artifacts from shorter lines
                queue!(
                    stdout,
                    cursor::MoveTo(0, line_num as u16),
                    crossterm::terminal::Clear(ClearType::UntilNewLine),
                    Print(current_line)
                )?;

                // Update previous_lines in-place, reusing String allocation when possible
                self.previous_lines[line_num].clear();
                self.previous_lines[line_num].push_str(current_line);
            }
        }

        // Clear any remaining lines if the new content is shorter
        for line_num in current_line_count..self.screen_height {
            if !self.previous_lines[line_num].is_empty() {
                queue!(
                    stdout,
                    cursor::MoveTo(0, line_num as u16),
                    crossterm::terminal::Clear(ClearType::CurrentLine)
                )?;
                self.previous_lines[line_num].clear();
            }
        }

        // Flush all queued updates at once
        stdout.flush()?;

        Ok(())
    }

    /// Force clear the entire screen (use sparingly, e.g., on startup or resize)
    pub fn force_clear(&mut self) -> std::io::Result<()> {
        let mut stdout = stdout();
        queue!(stdout, crossterm::terminal::Clear(ClearType::All))?;
        stdout.flush()?;

        // Reset previous state including hash to force re-render
        self.previous_lines.clear();
        self.previous_lines
            .resize(self.screen_height, String::new());
        self.previous_content_hash = 0;

        Ok(())
    }
}
