use std::fmt;

use rmux_proto::{
    MoveWindowTarget, PaneTarget, SelectLayoutTarget, SessionName, SplitWindowTarget, Target,
    WindowTarget,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct TargetSpec {
    raw: String,
    exact: Option<Target>,
}

impl TargetSpec {
    pub(crate) fn raw(&self) -> &str {
        &self.raw
    }

    pub(crate) fn exact(&self) -> Option<&Target> {
        self.exact.as_ref()
    }
}

impl fmt::Display for TargetSpec {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(&self.raw)
    }
}

impl PartialEq<SessionName> for TargetSpec {
    fn eq(&self, other: &SessionName) -> bool {
        matches!(self.exact(), Some(Target::Session(session_name)) if session_name == other)
    }
}

impl PartialEq<WindowTarget> for TargetSpec {
    fn eq(&self, other: &WindowTarget) -> bool {
        matches!(self.exact(), Some(Target::Window(target)) if target == other)
    }
}

impl PartialEq<PaneTarget> for TargetSpec {
    fn eq(&self, other: &PaneTarget) -> bool {
        matches!(self.exact(), Some(Target::Pane(target)) if target == other)
    }
}

impl PartialEq<Target> for TargetSpec {
    fn eq(&self, other: &Target) -> bool {
        self.exact().is_some_and(|target| target == other)
    }
}

impl PartialEq<MoveWindowTarget> for TargetSpec {
    fn eq(&self, other: &MoveWindowTarget) -> bool {
        match (self.exact(), other) {
            (Some(Target::Session(session_name)), MoveWindowTarget::Session(other)) => {
                session_name == other
            }
            (Some(Target::Window(target)), MoveWindowTarget::Window(other)) => target == other,
            _ => false,
        }
    }
}

impl PartialEq<SelectLayoutTarget> for TargetSpec {
    fn eq(&self, other: &SelectLayoutTarget) -> bool {
        match (self.exact(), other) {
            (Some(Target::Session(session_name)), SelectLayoutTarget::Session(other)) => {
                session_name == other
            }
            (Some(Target::Window(target)), SelectLayoutTarget::Window(other)) => target == other,
            _ => false,
        }
    }
}

impl PartialEq<SplitWindowTarget> for TargetSpec {
    fn eq(&self, other: &SplitWindowTarget) -> bool {
        match (self.exact(), other) {
            (Some(Target::Session(session_name)), SplitWindowTarget::Session(other)) => {
                session_name == other
            }
            (Some(Target::Pane(target)), SplitWindowTarget::Pane(other)) => target == other,
            _ => false,
        }
    }
}

pub(super) fn parse_session_name(value: &str) -> Result<SessionName, String> {
    SessionName::new(value.to_owned()).map_err(|error| error.to_string())
}

pub(crate) fn parse_target_spec(value: &str) -> Result<TargetSpec, String> {
    match Target::parse(value) {
        Ok(target) => Ok(TargetSpec {
            raw: value.to_owned(),
            exact: Some(target),
        }),
        Err(_) if is_runtime_resolved_target_shape(value) => Ok(TargetSpec {
            raw: value.to_owned(),
            exact: None,
        }),
        Err(error) => Err(error.to_string()),
    }
}

fn is_runtime_resolved_target_shape(value: &str) -> bool {
    !value.is_empty()
}

pub(super) fn parse_target(value: &str) -> Result<Target, String> {
    if let Some(session_name) = value.strip_suffix(':') {
        if !session_name.is_empty() {
            return parse_session_name(session_name).map(Target::Session);
        }
    }

    Target::parse(value).map_err(|error| error.to_string())
}
