//! Video export dialog — exports a folder of images as a slideshow video
//! with a user-selected audio track.
//!
//! Uses ffmpeg (external dependency) to encode the video. Supports two
//! codecs:
//! - WebM with VP9 + Opus (broad compatibility, good quality/size ratio)
//! - WebM with AV1 + Opus (better compression, slower encoding)
//!
//! The export flow:
//! 1. User right-clicks → "Export folder as video…"
//! 2. Dialog opens showing image count + folder name
//! 3. User selects audio file (any format ffmpeg supports)
//! 4. User selects output path (.webm)
//! 5. User picks codec and duration per image
//! 6. User clicks Export → ffmpeg runs in a background thread
//! 7. Dialog shows "Exporting…" then "Export complete" or error

use std::path::{Path, PathBuf};

use iced::widget::{button, column, container, row, text, text_input};
use iced::{Element, Length};

use super::icons::Icon;
use super::theme;

const PANEL_WIDTH: f32 = 460.0;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportCodec {
    VP9,
    AV1,
}

impl ExportCodec {
    pub fn ffmpeg_vcodec(self) -> &'static str {
        match self {
            ExportCodec::VP9 => "libvpx-vp9",
            ExportCodec::AV1 => "libaom-av1",
        }
    }

    pub fn label(self) -> &'static str {
        match self {
            ExportCodec::VP9 => "WebM (VP9)",
            ExportCodec::AV1 => "WebM (AV1)",
        }
    }
}

#[derive(Debug, Clone)]
pub struct ExportDialog {
    pub visible: bool,
    pub folder_path: Option<PathBuf>,
    pub image_count: usize,
    pub audio_path: Option<PathBuf>,
    pub output_path: Option<PathBuf>,
    pub codec: ExportCodec,
    pub duration_per_image: String,
    pub exporting: bool,
    pub result_message: Option<String>,
    pub result_is_error: bool,
}

#[derive(Debug, Clone)]
pub enum ExportMessage {
    SelectAudio,
    AudioSelected(Option<PathBuf>),
    SelectOutput,
    OutputSelected(Option<PathBuf>),
    CodecChanged(ExportCodec),
    DurationChanged(String),
    StartExport,
    ExportCompleted(Result<(), String>),
    Dismiss,
}

impl Default for ExportDialog {
    fn default() -> Self {
        Self {
            visible: false,
            folder_path: None,
            image_count: 0,
            audio_path: None,
            output_path: None,
            codec: ExportCodec::VP9,
            duration_per_image: "3.0".to_string(),
            exporting: false,
            result_message: None,
            result_is_error: false,
        }
    }
}

impl ExportDialog {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn show(&mut self, folder: PathBuf, image_count: usize) {
        self.folder_path = Some(folder);
        self.image_count = image_count;
        self.audio_path = None;
        self.output_path = None;
        self.codec = ExportCodec::VP9;
        self.duration_per_image = "3.0".to_string();
        self.exporting = false;
        self.result_message = None;
        self.result_is_error = false;
        self.visible = true;
    }

    pub fn dismiss(&mut self) {
        self.visible = false;
    }

    fn can_export(&self) -> bool {
        !self.exporting
            && self.audio_path.is_some()
            && self.output_path.is_some()
            && self.image_count > 0
            && self.duration_per_image.parse::<f32>().map(|d| d > 0.0).unwrap_or(false)
    }

    pub fn view(&self) -> Option<Element<'_, ExportMessage>> {
        if !self.visible {
            return None;
        }

        let folder_name = self
            .folder_path
            .as_ref()
            .and_then(|p| p.file_name())
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_else(|| "(unknown)".to_string());

        let title = row![
            Icon::Film.widget(18.0, &theme::text_secondary_hex()),
            text("Export Slideshow to Video")
                .color(theme::TEXT_PRIMARY)
                .size(16),
        ]
        .align_y(iced::Alignment::Center)
        .spacing(8.0);

        let info = text(format!(
            "{} images from {}",
            self.image_count, folder_name
        ))
        .color(theme::TEXT_SECONDARY)
        .size(12);

        // Audio file selector
        let audio_label = text("Audio track:")
            .color(theme::TEXT_SECONDARY)
            .size(11);

        let audio_btn_label = self
            .audio_path
            .as_ref()
            .and_then(|p| p.file_name())
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_else(|| "Select audio file…".to_string());

        let audio_btn = button(
            row![
                audio_label.color(theme::TEXT_PRIMARY).size(12),
                text("").width(Length::Fill),
                text(audio_btn_label)
                    .color(theme::TEXT_DIM)
                    .size(11),
            ]
            .align_y(iced::Alignment::Center),
        )
        .on_press(ExportMessage::SelectAudio)
        .width(Length::Fill)
        .padding(iced::Padding {
            top: 8.0,
            right: 12.0,
            bottom: 8.0,
            left: 12.0,
        })
        .style(|_theme, status| {
            let bg = match status {
                button::Status::Hovered => Some(theme::BG_CHROME_HOVER),
                _ => Some(theme::BG_IMAGE_AREA.into()),
            };
            button::Style {
                background: bg.map(iced::Background::Color),
                border: iced::Border {
                    color: theme::BG_CHROME_BORDER,
                    width: 1.0,
                    radius: 4.0.into(),
                },
                ..Default::default()
            }
        });

        // Output file selector
        let output_btn_label = self
            .output_path
            .as_ref()
            .and_then(|p| p.file_name())
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_else(|| "Select output file…".to_string());

        let output_btn = button(
            row![
                text("Output file:").color(theme::TEXT_PRIMARY).size(12),
                text("").width(Length::Fill),
                text(output_btn_label)
                    .color(theme::TEXT_DIM)
                    .size(11),
            ]
            .align_y(iced::Alignment::Center),
        )
        .on_press(ExportMessage::SelectOutput)
        .width(Length::Fill)
        .padding(iced::Padding {
            top: 8.0,
            right: 12.0,
            bottom: 8.0,
            left: 12.0,
        })
        .style(|_theme, status| {
            let bg = match status {
                button::Status::Hovered => Some(theme::BG_CHROME_HOVER),
                _ => Some(theme::BG_IMAGE_AREA.into()),
            };
            button::Style {
                background: bg.map(iced::Background::Color),
                border: iced::Border {
                    color: theme::BG_CHROME_BORDER,
                    width: 1.0,
                    radius: 4.0.into(),
                },
                ..Default::default()
            }
        });

        // Codec selector
        let codec_label = text("Codec:")
            .color(theme::TEXT_SECONDARY)
            .size(11);

        let vp9_btn = button(text("VP9").color(theme::TEXT_PRIMARY).size(11))
            .on_press(ExportMessage::CodecChanged(ExportCodec::VP9))
            .padding(iced::Padding {
                top: 5.0,
                right: 14.0,
                bottom: 5.0,
                left: 14.0,
            })
            .style(move |_theme, _status| {
                let active = self.codec == ExportCodec::VP9;
                button::Style {
                    background: Some(
                        if active {
                            theme::ACCENT_DIM
                        } else {
                            theme::BG_IMAGE_AREA
                        }
                        .into(),
                    ),
                    border: iced::Border {
                        color: if active {
                            theme::ACCENT
                        } else {
                            theme::BG_CHROME_BORDER
                        },
                        width: 1.0,
                        radius: 4.0.into(),
                    },
                    ..Default::default()
                }
            });

        let av1_btn = button(text("AV1").color(theme::TEXT_PRIMARY).size(11))
            .on_press(ExportMessage::CodecChanged(ExportCodec::AV1))
            .padding(iced::Padding {
                top: 5.0,
                right: 14.0,
                bottom: 5.0,
                left: 14.0,
            })
            .style(move |_theme, _status| {
                let active = self.codec == ExportCodec::AV1;
                button::Style {
                    background: Some(
                        if active {
                            theme::ACCENT_DIM
                        } else {
                            theme::BG_IMAGE_AREA
                        }
                        .into(),
                    ),
                    border: iced::Border {
                        color: if active {
                            theme::ACCENT
                        } else {
                            theme::BG_CHROME_BORDER
                        },
                        width: 1.0,
                        radius: 4.0.into(),
                    },
                    ..Default::default()
                }
            });

        let codec_row = row![
            codec_label,
            text("").width(Length::Fixed(8.0)),
            vp9_btn,
            text("").width(Length::Fixed(4.0)),
            av1_btn,
        ]
        .align_y(iced::Alignment::Center);

        // Duration input
        let duration_row = row![
            text("Seconds per image:")
                .color(theme::TEXT_SECONDARY)
                .size(11),
            text("").width(Length::Fixed(8.0)),
            text_input("3.0", &self.duration_per_image)
                .on_input(ExportMessage::DurationChanged)
                .width(Length::Fixed(80.0))
                .size(11),
        ]
        .align_y(iced::Alignment::Center);

        // Result message (if any)
        let result_el: Option<Element<'_, ExportMessage>> = self.result_message.as_ref().map(|msg| {
            let color = if self.result_is_error {
                theme::DANGER
            } else {
                theme::ACCENT
            };
            text(msg.clone()).color(color).size(12).into()
        });

        // Action buttons
        let action_label = if self.exporting {
            "Exporting…"
        } else if self.result_message.is_some() {
            "Close"
        } else {
            "Export"
        };

        let action_msg = if self.result_message.is_some() {
            ExportMessage::Dismiss
        } else {
            ExportMessage::StartExport
        };

        let can_press = self.exporting || self.can_export() || self.result_message.is_some();

        let export_btn = button(
            text(action_label)
                .color(theme::TEXT_PRIMARY)
                .size(12),
        )
        .on_press_maybe(if can_press { Some(action_msg) } else { None })
        .padding(iced::Padding {
            top: 8.0,
            right: 24.0,
            bottom: 8.0,
            left: 24.0,
        })
        .style(|_theme, status| {
            let bg = match status {
                button::Status::Hovered => Some(theme::ACCENT_DIM),
                _ => Some(theme::ACCENT.into()),
            };
            button::Style {
                background: bg.map(iced::Background::Color),
                border: iced::Border {
                    radius: 4.0.into(),
                    ..Default::default()
                },
                ..Default::default()
            }
        });

        let cancel_btn = button(
            text("Cancel").color(theme::TEXT_PRIMARY).size(12),
        )
        .on_press(ExportMessage::Dismiss)
        .padding(iced::Padding {
            top: 8.0,
            right: 24.0,
            bottom: 8.0,
            left: 24.0,
        })
        .style(|_theme, status| {
            let bg = match status {
                button::Status::Hovered => Some(theme::BG_CHROME_HOVER),
                _ => Some(theme::BG_IMAGE_AREA.into()),
            };
            button::Style {
                background: bg.map(iced::Background::Color),
                border: iced::Border {
                    color: theme::BG_CHROME_BORDER,
                    width: 1.0,
                    radius: 4.0.into(),
                },
                ..Default::default()
            }
        });

        let action_row = row![
            text("").width(Length::Fill),
            cancel_btn,
            text("").width(Length::Fixed(8.0)),
            export_btn,
        ]
        .align_y(iced::Alignment::Center);

        // Assemble
        let mut children: Vec<Element<'_, ExportMessage>> = vec![
            title.into(),
            text("").height(Length::Fixed(4.0)).into(),
            info.into(),
            text("").height(Length::Fixed(16.0)).into(),
            audio_btn.into(),
            text("").height(Length::Fixed(8.0)).into(),
            output_btn.into(),
            text("").height(Length::Fixed(12.0)).into(),
            codec_row.into(),
            text("").height(Length::Fixed(8.0)).into(),
            duration_row.into(),
        ];

        if let Some(r) = result_el {
            children.push(text("").height(Length::Fixed(12.0)).into());
            children.push(r);
        }

        children.push(text("").height(Length::Fixed(16.0)).into());
        children.push(action_row.into());

        let content = column(children)
            .padding(iced::Padding {
                top: 20.0,
                right: 20.0,
                bottom: 20.0,
                left: 20.0,
            })
            .spacing(0.0);

        let card = container(content)
            .max_width(PANEL_WIDTH)
            .style(|_| container::Style {
                background: Some(theme::BG_CHROME.into()),
                border: iced::Border {
                    color: theme::BG_CHROME_BORDER,
                    width: 1.0,
                    radius: 8.0.into(),
                },
                shadow: iced::Shadow {
                    color: iced::Color::from_rgba(0.0, 0.0, 0.0, 0.7),
                    offset: iced::Vector::new(0.0, 8.0),
                    blur_radius: 32.0,
                },
                ..Default::default()
            });

        let centered = container(card)
            .width(Length::Fill)
            .height(Length::Fill)
            .align_x(iced::Alignment::Center)
            .align_y(iced::Alignment::Center)
            .style(|_| container::Style {
                background: Some(
                    iced::Color::from_rgba(0.0, 0.0, 0.0, 0.75).into(),
                ),
                ..Default::default()
            });

        Some(centered.into())
    }
}

/// Run ffmpeg to create a slideshow video from a list of images with an
/// audio track. Called from a background thread via Task::perform.
///
/// Uses the concat demuxer: writes a temporary file list, then invokes:
///   ffmpeg -y -f concat -safe 0 -i filelist.txt -i audio.mp3 \
///     -c:v <vcodec> -crf 30 -b:v 0 -c:a libopus -shortest output.webm
pub fn run_ffmpeg_export(
    images: &[PathBuf],
    audio: &Path,
    output: &Path,
    codec: ExportCodec,
    duration: f32,
) -> Result<(), String> {
    if images.is_empty() {
        return Err("No images to export".to_string());
    }

    // Build the concat file list.
    let mut filelist = String::new();
    for img in images {
        let path_str = img.display().to_string();
        // Escape single quotes in paths for the concat demuxer.
        let escaped = path_str.replace('\'', r"'\''");
        filelist.push_str(&format!("file '{}'\n", escaped));
        filelist.push_str(&format!("duration {:.1}\n", duration));
    }
    // Repeat the last file without a duration (ffmpeg concat quirk:
    // the last duration entry is ignored unless the file is repeated).
    if let Some(last) = images.last() {
        let path_str = last.display().to_string();
        let escaped = path_str.replace('\'', r"'\''");
        filelist.push_str(&format!("file '{}'\n", escaped));
    }

    let list_path = std::env::temp_dir().join("marten_export_list.txt");
    std::fs::write(&list_path, &filelist)
        .map_err(|e| format!("Could not write temp file list: {e}"))?;

    let vcodec = codec.ffmpeg_vcodec();

    let output = std::process::Command::new("ffmpeg")
        .args(["-y", "-f", "concat", "-safe", "0", "-i"])
        .arg(&list_path)
        .args(["-i"])
        .arg(audio)
        .args([
            "-c:v", vcodec,
            "-crf", "30",
            "-b:v", "0",
            "-c:a", "libopus",
            "-shortest",
        ])
        .arg(output)
        .output()
        .map_err(|e| {
            if e.kind() == std::io::ErrorKind::NotFound {
                "ffmpeg not found. Install ffmpeg to use video export.".to_string()
            } else {
                format!("Failed to launch ffmpeg: {e}")
            }
        })?;

    let _ = std::fs::remove_file(&list_path);

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        let trimmed = stderr.lines().last().unwrap_or("Unknown ffmpeg error");
        return Err(trimmed.to_string());
    }

    Ok(())
}
