// Back - An extremely simple git issue tracking system. Inspired by tvix's
// panettone
//
// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This file is part of Back.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/agpl.txt>.

use std::fmt::Display;

use entity::{Entity, Id};
use identity::Author;
use label::Label;
use operation::Operation;
use serde_json::Value;

use super::format::{MarkDown, TimeStamp};

pub mod entity;
pub mod identity;
pub mod label;
pub mod operation;

#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum Status {
    Open,
    Closed,
}
impl From<&Value> for Status {
    fn from(value: &Value) -> Self {
        match value.as_u64().expect("This should be a integer") {
            1 => Self::Open,
            2 => Self::Closed,
            other => unimplemented!("Invalid status string: '{other}'"),
        }
    }
}
impl Display for Status {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Status::Open => f.write_str("Open"),
            Status::Closed => f.write_str("Closed"),
        }
    }
}

#[derive(Debug)]
pub struct CollapsedIssue {
    pub id: Id,
    pub author: Author,
    pub timestamp: TimeStamp,
    pub title: MarkDown,
    pub message: MarkDown,
    pub comments: Vec<Comment>,
    pub status: Status,
    pub last_status_change: TimeStamp,
    pub labels: Vec<Label>,
}
impl From<RawCollapsedIssue> for CollapsedIssue {
    fn from(r: RawCollapsedIssue) -> Self {
        macro_rules! get {
            ($name:ident) => {
                r.$name.expect(concat!(
                    "'",
                    stringify!($name),
                    "' is unset, when trying to collapes an issue! (This is likely a bug)"
                ))
            };
        }

        Self {
            id: get! {id},
            author: get! {author},
            timestamp: get! {timestamp},
            title: get! {title},
            message: get! {message},
            comments: r.comments,
            status: get! {status},
            last_status_change: get! {last_status_change},
            labels: r.labels,
        }
    }
}

#[derive(Debug)]
pub struct Comment {
    pub id: Id,
    pub author: Author,
    pub timestamp: TimeStamp,
    pub message: MarkDown,
}

#[derive(Debug, Default)]
pub struct RawCollapsedIssue {
    pub id: Option<Id>,
    pub author: Option<Author>,
    pub timestamp: Option<TimeStamp>,
    pub title: Option<MarkDown>,
    pub message: Option<MarkDown>,
    pub status: Option<Status>,
    pub last_status_change: Option<TimeStamp>,

    // NOTE(@bpeetz): These values set here already, because an issue without these
    // would be perfectly valid. <2024-12-26>
    pub labels: Vec<Label>,
    pub comments: Vec<Comment>,
}

impl RawCollapsedIssue {
    pub fn append_entity(&mut self, entity: Entity) {
        for op in entity.operations {
            match op {
                Operation::AddComment { timestamp, message } => {
                    self.comments.push(Comment {
                        id: entity.id.clone(),
                        author: entity.author.clone(),
                        timestamp,
                        message,
                    });
                }
                Operation::Create {
                    timestamp,
                    title,
                    message,
                } => {
                    self.id = Some(entity.id.clone());
                    self.author = Some(entity.author.clone());
                    self.timestamp = Some(timestamp.clone());
                    self.title = Some(title);
                    self.message = Some(message);
                    self.status = Some(Status::Open); // This is the default in git_bug
                    self.last_status_change = Some(timestamp);
                }
                Operation::EditComment {
                    timestamp,
                    target,
                    message,
                } => {
                    let comments = &mut self.comments;

                    let target_comment = comments
                        .iter_mut()
                        .find(|comment| comment.id == target)
                        .expect("The target must be a valid comment");

                    // TODO(@bpeetz): We should probably set a `edited = true` flag here. <2024-12-26>
                    // TODO(@bpeetz): Should we also change the author? <2024-12-26>

                    target_comment.timestamp = timestamp;
                    target_comment.message = message;
                }
                Operation::LabelChange {
                    timestamp: _,
                    added,
                    removed,
                } => {
                    let labels = self.labels.clone();

                    self.labels = labels
                        .into_iter()
                        .filter(|val| !removed.contains(val))
                        .chain(added.into_iter())
                        .collect();
                }
                Operation::SetStatus { timestamp, status } => {
                    self.status = Some(status);
                    self.last_status_change = Some(timestamp);
                }
                Operation::SetTitle {
                    timestamp: _,
                    title,
                    was: _,
                } => {
                    self.title = Some(title);
                }

                Operation::NoOp {} => unimplemented!(),
                Operation::SetMetadata {} => unimplemented!(),
            }
        }
    }
}