use std::{
    cmp::Reverse,
    collections::{HashMap, HashSet},
    mem,
    ops::{Deref, DerefMut, Range},
    sync::Arc,
};

use rowan::{TextRange, TextSize};
use rue_ast::AstNode;
use rue_diagnostic::{Diagnostic, DiagnosticKind, Name, Source, SourceKind, SrcLoc};
use rue_hir::{
    Builtins, Constraint, Database, Declaration, ImportId, Scope, ScopeId, Symbol, SymbolId,
    TypePath, Value, replace_type,
};
use rue_options::CompilerOptions;
use rue_parser::{SyntaxNode, SyntaxToken};
use rue_types::{Check, CheckError, Comparison, Type, TypeId};

use crate::{FileTree, SyntaxItem, SyntaxItemKind, SyntaxMap};

#[derive(Debug, Clone)]
pub struct Compiler {
    options: CompilerOptions,
    source: Source,
    diagnostics: Vec<Diagnostic>,
    db: Database,
    syntax_map: SyntaxMap,
    scope_stack: Vec<(TextSize, ScopeId)>,
    module_stack: Vec<SymbolId>,
    builtins: Builtins,
    defaults: HashMap<TypeId, HashMap<String, Value>>,
    declaration_stack: Vec<Declaration>,
    registered_scopes: HashSet<ScopeId>,
}

impl Deref for Compiler {
    type Target = Database;

    fn deref(&self) -> &Self::Target {
        &self.db
    }
}

impl DerefMut for Compiler {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.db
    }
}

impl Compiler {
    pub fn new(options: CompilerOptions) -> Self {
        let mut db = Database::new();

        let builtins = Builtins::new(&mut db);

        let mut ctx = Self {
            options,
            source: Source::new(Arc::from(""), SourceKind::Std("<placeholder>".to_string())),
            diagnostics: Vec::new(),
            db,
            syntax_map: SyntaxMap::new(),
            scope_stack: vec![(TextSize::from(0), builtins.scope)],
            module_stack: Vec::new(),
            builtins,
            defaults: HashMap::new(),
            declaration_stack: Vec::new(),
            registered_scopes: HashSet::new(),
        };

        if options.std {
            let tree = FileTree::load_std(&mut ctx).unwrap();
            tree.compile_impl(&mut ctx, false);
            let prelude_file = tree
                .find(&SourceKind::Std("prelude.rue".to_string()))
                .unwrap();
            let prelude_scope = ctx.module(prelude_file.module).scope;

            let prelude = ctx.alloc_child_scope();

            for (name, symbol) in ctx
                .scope(prelude_scope)
                .exported_symbols()
                .map(|(name, symbol)| (name.to_string(), symbol))
                .collect::<Vec<_>>()
            {
                ctx.scope_mut(prelude).insert_symbol(name, symbol, false);
            }

            for (name, ty) in ctx
                .scope(prelude_scope)
                .exported_types()
                .map(|(name, ty)| (name.to_string(), ty))
                .collect::<Vec<_>>()
            {
                ctx.scope_mut(prelude).insert_type(name, ty, false);
            }

            ctx.push_scope(prelude, prelude_file.document.syntax().text_range().start());
        }

        ctx
    }

    pub fn source(&self) -> &Source {
        &self.source
    }

    pub fn options(&self) -> &CompilerOptions {
        &self.options
    }

    pub fn set_source(&mut self, source: Source) {
        self.source = source;
    }

    pub fn scope_stack(&self) -> Vec<ScopeId> {
        self.scope_stack.iter().map(|(_, scope)| *scope).collect()
    }

    pub fn syntax_map(&self) -> &SyntaxMap {
        &self.syntax_map
    }

    pub fn add_syntax(&mut self, kind: SyntaxItemKind, span: TextRange) {
        self.add_syntax_for_source(kind, span, self.source.kind.clone());
    }

    pub fn add_syntax_for_source(
        &mut self,
        kind: SyntaxItemKind,
        span: TextRange,
        source_kind: SourceKind,
    ) {
        self.syntax_map
            .add_item(SyntaxItem::new(kind, span, source_kind));
    }

    pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
        mem::take(&mut self.diagnostics)
    }

    pub fn builtins(&self) -> &Builtins {
        &self.builtins
    }

    pub fn diagnostic(&mut self, node: &impl GetTextRange, kind: DiagnosticKind) {
        let srcloc = self.srcloc(node);
        self.diagnostic_at(srcloc, kind);
    }

    pub fn diagnostic_name(&mut self, name: &Name, kind: DiagnosticKind) {
        if let Some(srcloc) = name.srcloc().cloned() {
            self.diagnostic_at(srcloc, kind);
        }
    }

    pub fn diagnostic_at(&mut self, srcloc: SrcLoc, kind: DiagnosticKind) {
        self.diagnostics.push(Diagnostic::new(srcloc, kind));
    }

    pub fn srcloc(&self, node: &impl GetTextRange) -> SrcLoc {
        let range = node.text_range();
        let span: Range<usize> = range.start().into()..range.end().into();
        SrcLoc::new(self.source.clone(), span)
    }

    pub fn local_name(&self, token: &SyntaxToken) -> Name {
        let srcloc = self.srcloc(token);
        Name::new(token.text().to_string(), Some(srcloc))
    }

    pub fn extend_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) {
        self.diagnostics.extend(diagnostics);
    }

    pub fn alloc_child_scope(&mut self) -> ScopeId {
        let parent_scope = self.last_scope_id();
        self.alloc_scope(Scope::new(Some(parent_scope)))
    }

    pub fn push_scope(&mut self, scope: ScopeId, start: TextSize) {
        self.scope_stack.push((start, scope));
    }

    pub fn pop_scope(&mut self, end: TextSize) {
        let (start, scope) = self.scope_stack.pop().unwrap();

        if !self.registered_scopes.insert(scope) {
            return;
        }

        self.add_syntax(SyntaxItemKind::Scope(scope), TextRange::new(start, end));
    }

    pub fn last_scope(&self) -> &Scope {
        let scope = *self.scope_stack.last().unwrap();
        self.scope(scope.1)
    }

    pub fn last_scope_mut(&mut self) -> &mut Scope {
        let scope = *self.scope_stack.last().unwrap();
        self.scope_mut(scope.1)
    }

    pub fn last_scope_id(&self) -> ScopeId {
        self.scope_stack.last().unwrap().1
    }

    pub fn push_module(&mut self, module: SymbolId) {
        self.module_stack.push(module);
    }

    pub fn pop_module(&mut self) {
        self.module_stack.pop().unwrap();
    }

    pub fn parent_module_stack(&self) -> &[SymbolId] {
        if self.module_stack.is_empty() {
            return &[];
        }

        &self.module_stack[..self.module_stack.len() - 1]
    }

    pub fn resolve_symbol_in(
        &self,
        scope: ScopeId,
        name: &str,
    ) -> Option<(SymbolId, Option<ImportId>)> {
        let mut current = Some(scope);

        while let Some(scope) = current {
            if let Some(symbol) = self.scope(scope).symbol(name) {
                return Some((symbol, self.scope(scope).symbol_import(symbol)));
            }
            current = self.scope(scope).parent();
        }

        None
    }

    pub fn resolve_type_in(
        &self,
        scope: ScopeId,
        name: &str,
    ) -> Option<(TypeId, Option<ImportId>)> {
        let mut current = Some(scope);

        while let Some(scope) = current {
            if let Some(ty) = self.scope(scope).ty(name) {
                return Some((ty, self.scope(scope).type_import(ty)));
            }
            current = self.scope(scope).parent();
        }

        None
    }

    pub fn type_name(&mut self, ty: TypeId) -> String {
        let current = self.last_scope_id();
        self.type_name_in(current, ty)
    }

    pub fn type_name_in(&mut self, scope: ScopeId, ty: TypeId) -> String {
        let mut current = Some(scope);

        while let Some(scope) = current {
            if let Some(name) = self.scope(scope).type_name(ty) {
                return name.to_string();
            }
            current = self.scope(scope).parent();
        }

        rue_types::stringify(self.db.types_mut(), ty)
    }

    pub fn symbol_name(&self, symbol: SymbolId) -> String {
        let mut current = Some(self.last_scope_id());

        while let Some(scope) = current {
            if let Some(name) = self.scope(scope).symbol_name(symbol) {
                return name.to_string();
            }
            current = self.scope(scope).parent();
        }

        self.symbol(symbol)
            .name()
            .map_or_else(|| "{unknown}".to_string(), |name| name.text().to_string())
    }

    pub fn symbol_type(&self, symbol: SymbolId) -> TypeId {
        let current = self.last_scope_id();
        self.symbol_type_in(current, symbol)
    }

    pub fn symbol_type_in(&self, scope: ScopeId, symbol: SymbolId) -> TypeId {
        let mut current = Some(scope);

        while let Some(scope) = current {
            if let Some(ty) = self.scope(scope).symbol_override_type(symbol) {
                return ty;
            }
            current = self.scope(scope).parent();
        }

        match self.symbol(symbol) {
            Symbol::Unresolved | Symbol::Module(_) | Symbol::Builtin(_) => {
                self.builtins().unresolved.ty
            }
            Symbol::Function(function) => function.ty,
            Symbol::Parameter(parameter) => parameter.ty,
            Symbol::Constant(constant) => constant.value.ty,
            Symbol::Binding(binding) => binding.value.ty,
        }
    }

    pub fn push_mappings(
        &mut self,
        mappings: HashMap<SymbolId, HashMap<Vec<TypePath>, TypeId>>,
        start: TextSize,
    ) -> usize {
        let scope = self.alloc_child_scope();

        for (symbol, paths) in mappings {
            let mut ty = self.symbol_type(symbol);

            let mut paths = paths.into_iter().collect::<Vec<_>>();
            paths.sort_by_key(|(path, _)| (Reverse(path.len()), path.last().copied()));

            for (path, replacement) in paths {
                ty = replace_type(&mut self.db, ty, replacement, &path);
            }

            self.scope_mut(scope).override_symbol_type(symbol, ty);
        }

        let index = self.scope_stack.len();
        self.push_scope(scope, start);
        index
    }

    pub fn mapping_checkpoint(&self) -> usize {
        self.scope_stack.len()
    }

    pub fn revert_mappings(&mut self, index: usize, end: TextSize) {
        while self.scope_stack.len() > index {
            self.pop_scope(end);
        }
    }

    pub fn is_assignable(&mut self, from: TypeId, to: TypeId) -> bool {
        let comparison = rue_types::compare(self.db.types_mut(), &self.builtins.types, from, to);
        comparison == Comparison::Assign
    }

    pub fn is_castable(&mut self, from: TypeId, to: TypeId) -> bool {
        let comparison = rue_types::compare(self.db.types_mut(), &self.builtins.types, from, to);
        matches!(comparison, Comparison::Assign | Comparison::Cast)
    }

    pub fn assign_type(&mut self, node: &impl GetTextRange, from: TypeId, to: TypeId) {
        self.compare_type(node, from, to, false, None);
    }

    pub fn cast_type(&mut self, node: &impl GetTextRange, from: TypeId, to: TypeId) {
        self.compare_type(node, from, to, true, None);
    }

    pub fn guard_type(&mut self, node: &impl GetTextRange, from: TypeId, to: TypeId) -> Constraint {
        let check = match rue_types::check(self.db.types_mut(), &self.builtins.types, from, to) {
            Ok(check) => check,
            Err(CheckError::DepthExceeded) => {
                self.diagnostic(node, DiagnosticKind::TypeCheckDepthExceeded);
                return Constraint::new(Check::Impossible);
            }
            Err(CheckError::FunctionType) => {
                self.diagnostic(node, DiagnosticKind::FunctionTypeCheck);
                return Constraint::new(Check::Impossible);
            }
        };

        let from_name = self.type_name(from);
        let to_name = self.type_name(to);

        if check == Check::None {
            self.diagnostic(node, DiagnosticKind::UnnecessaryGuard(from_name, to_name));
        } else if check == Check::Impossible {
            self.diagnostic(node, DiagnosticKind::IncompatibleGuard(from_name, to_name));
        }

        let else_id = rue_types::subtract(self.db.types_mut(), &self.builtins.types, from, to);

        Constraint::new(check).with_else(else_id)
    }

    pub fn check_condition(&mut self, node: &impl GetTextRange, ty: TypeId) {
        if self.is_castable(ty, self.builtins().types.bool_true) {
            self.diagnostic(node, DiagnosticKind::AlwaysTrueCondition);
        } else if self.is_castable(ty, self.builtins().types.bool_false) {
            self.diagnostic(node, DiagnosticKind::AlwaysFalseCondition);
        } else {
            self.assign_type(node, ty, self.builtins().types.bool);
        }
    }

    pub fn infer_type(
        &mut self,
        node: &impl GetTextRange,
        from: TypeId,
        to: TypeId,
        infer: &mut HashMap<TypeId, Vec<TypeId>>,
    ) {
        self.compare_type(node, from, to, false, Some(infer));
    }

    fn compare_type(
        &mut self,
        node: &impl GetTextRange,
        from: TypeId,
        to: TypeId,
        cast: bool,
        infer: Option<&mut HashMap<TypeId, Vec<TypeId>>>,
    ) {
        let comparison = rue_types::compare_with_inference(
            self.db.types_mut(),
            &self.builtins.types,
            from,
            to,
            infer,
        );

        match comparison {
            Comparison::Assign => {
                if cast
                    && rue_types::compare_with_inference(
                        self.db.types_mut(),
                        &self.builtins.types,
                        to,
                        from,
                        None,
                    ) == Comparison::Assign
                {
                    let from = self.type_name(from);
                    let to = self.type_name(to);

                    self.diagnostic(node, DiagnosticKind::UnnecessaryCast(from, to));
                }
            }
            Comparison::Cast => {
                if !cast {
                    let from = self.type_name(from);
                    let to = self.type_name(to);
                    self.diagnostic(node, DiagnosticKind::UnassignableType(from, to));
                }
            }
            Comparison::Invalid => {
                let check =
                    match rue_types::check(self.db.types_mut(), &self.builtins.types, from, to) {
                        Ok(check) => check,
                        Err(CheckError::DepthExceeded | CheckError::FunctionType) => {
                            Check::Impossible
                        }
                    };

                let from = self.type_name(from);
                let to = self.type_name(to);

                if check != Check::Impossible {
                    self.diagnostic(node, DiagnosticKind::UnconstrainableComparison(from, to));
                } else if cast {
                    self.diagnostic(node, DiagnosticKind::IncompatibleCast(from, to));
                } else {
                    self.diagnostic(node, DiagnosticKind::IncompatibleType(from, to));
                }
            }
        }
    }

    pub fn insert_default_field(&mut self, ty: TypeId, name: String, value: Value) {
        self.defaults.entry(ty).or_default().insert(name, value);
    }

    pub fn default_field(&self, ty: TypeId, name: &str) -> Option<Value> {
        self.defaults
            .get(&ty)
            .and_then(|map| map.get(name).cloned())
    }

    pub fn push_declaration(&mut self, declaration: Declaration) {
        if let Some(last) = self.declaration_stack.last() {
            self.db.add_declaration(*last, declaration);
        }

        self.declaration_stack.push(declaration);

        if self.source.kind.check_unused() {
            self.db.add_relevant_declaration(declaration);
        }
    }

    pub fn pop_declaration(&mut self) {
        self.declaration_stack.pop().unwrap();
    }

    pub fn reference(&mut self, reference: Declaration, import: Option<ImportId>) {
        if let Some(last) = self.declaration_stack.last() {
            self.db.add_reference(*last, reference);
        }

        if let Some(import) = import {
            self.db.add_import_reference(import, reference);
        }
    }

    pub fn declaration_span(&mut self, declaration: Declaration, span: TextRange) {
        self.add_syntax(
            match declaration {
                Declaration::Symbol(symbol) => SyntaxItemKind::SymbolDeclaration(symbol),
                Declaration::Type(ty) => SyntaxItemKind::TypeDeclaration(ty),
            },
            span,
        );
    }

    pub fn reference_span(&mut self, reference: Declaration, span: TextRange) {
        self.add_syntax(
            match reference {
                Declaration::Symbol(symbol) => SyntaxItemKind::SymbolReference(symbol),
                Declaration::Type(ty) => SyntaxItemKind::TypeReference(ty),
            },
            span,
        );
    }

    pub fn is_unresolved(&mut self, ty: TypeId) -> bool {
        let semantic = rue_types::unwrap_semantic(self.db.types_mut(), ty, true);
        matches!(self.ty(semantic), Type::Unresolved)
    }
}

pub trait GetTextRange {
    fn text_range(&self) -> TextRange;
}

impl GetTextRange for TextRange {
    fn text_range(&self) -> TextRange {
        *self
    }
}

impl GetTextRange for SyntaxNode {
    fn text_range(&self) -> TextRange {
        self.text_range()
    }
}

impl GetTextRange for SyntaxToken {
    fn text_range(&self) -> TextRange {
        self.text_range()
    }
}
