#![allow(warnings)]
// Mech
//=============================================================================

// Prelude
// ----------------------------------------------------------------------------

pub extern crate mech_core as core;
pub extern crate mech_syntax as syntax;

pub use mech_core::*;
use mech_core::nodes::Program;
pub use mech_interpreter::Interpreter;

extern crate colored;
use colored::*;

extern crate bincode;
use std::io::{Write, BufReader, BufWriter, stdout};
use std::fs::{OpenOptions, File, canonicalize, create_dir};
use crossterm::{
  ExecutableCommand, QueueableCommand,
  terminal, cursor, style::Print,
};

use tabled::{
  builder::Builder,
  grid::config::HorizontalLine,
  settings::{object::Rows,Panel, Span, Alignment, Modify, Style},
  Tabled,
};
use std::path::{Path, PathBuf};
use std::io;
use std::io::prelude::*;
use std::time::{Duration, Instant, SystemTime};
use std::thread::{self, JoinHandle};
use std::sync::Mutex;
use std::sync::RwLock;
//use websocket::sync::Server;
use std::net::{SocketAddr, UdpSocket, TcpListener, TcpStream};
use std::collections::HashMap;
use crossbeam_channel::Sender;
use crossbeam_channel::{unbounded, Receiver};
use std::{fs,env};

#[cfg(feature = "wasm")]
use web_sys::{Crypto, Window, console};
use rand::rngs::OsRng;
use rand::Rng;
use notify::{recommended_watcher, Event, RecursiveMode, Result as NResult, Watcher};
use std::sync::mpsc;
use std::sync::Arc;
use std::collections::HashSet;

#[cfg(feature = "repl")]
mod repl;
#[cfg(feature = "serve")]
mod serve;
#[cfg(feature = "run")]
mod run;
#[cfg(feature = "mechfs")]
mod mechfs;

#[cfg(feature = "repl")]
pub use self::repl::*;
#[cfg(feature = "serve")]
pub use self::serve::*;
#[cfg(feature = "run")]
pub use self::run::*;
#[cfg(feature = "mechfs")]
pub use self::mechfs::*;

pub use mech_core::*;
pub use mech_syntax::*;

// Print a prompt 
// 4, 8, 15, 16, 23, 42
pub fn print_prompt() {
  stdout().flush();
  print!("{}", ">: ".truecolor(246,192,78));
  stdout().flush();
}

// Generate a new id for creating unique owner ids
#[cfg(not(feature = "wasm"))]
pub fn generate_uuid() -> u64 {
  rand::rng().random()
}

#[cfg(feature = "wasm")]
pub fn generate_uuid() -> u64 {
  let mut rng = WebCryptoRng{};
  rng.next_u64()
}

pub fn mech_table_style() -> Style<(),(),(),(),(),(),2,0> {
  Style::empty()
    .horizontals([
      (1, HorizontalLine::filled('-').into()),
      (2, HorizontalLine::filled('-').into()),
    ])
}

pub fn help() -> String {
  let mut builder = Builder::default();
  builder.push_record(vec!["Command".bright_white().to_string(),"Short".bright_white().to_string(),"Parameters".to_string(),"Description".to_string()]);
  builder.push_record(vec![
    ":cd".bright_yellow().to_string(),
    "".bright_yellow().to_string(),
    "target-path".to_string(),
    "Change directory to target-path, which can be relative or absolute.".to_string()
  ]);
  builder.push_record(vec![
    ":clc".bright_yellow().to_string(),
    ":c".bright_yellow().to_string(),
    "".to_string(),
    "Clear the screen".to_string()
  ]);
  builder.push_record(vec![
    ":clear".bright_yellow().to_string(),
    "".bright_yellow().to_string(),
    "[target-variable]".to_string(),
    "Clear the interpreter state".to_string()
  ]);
  builder.push_record(vec![
    ":docs".bright_yellow().to_string(),
    ":d".bright_yellow().to_string(),
    "[doc-name]".to_string(),
    "Search documentation for a given doc".to_string()
  ]);
  builder.push_record(vec![
    ":help".bright_yellow().to_string(),
    ":h".bright_yellow().to_string(),
    "".to_string(),
    "Display this help message.".to_string()
  ]);
  builder.push_record(vec![
    ":load".bright_yellow().to_string(),
    "".bright_yellow().to_string(),
    "file-path".to_string(),
    "Load a file.".to_string()
  ]);
  builder.push_record(vec![
    ":ls".bright_yellow().to_string(),
    "".bright_yellow().to_string(),
    "[target-path]".to_string(),
    "List directory contents. Optionally supply a target path.".to_string()
  ]);
  builder.push_record(vec![
    ":plan".bright_yellow().to_string(),
    ":p".bright_yellow().to_string(),
    "".to_string(),
    "Display each step of the current plan.".to_string()
    ]);
  builder.push_record(vec![
    ":quit".bright_yellow().to_string(),
    ":q".bright_yellow().to_string(),
    "".to_string(),
    "Quit the REPL.".to_string()
  ]);
  builder.push_record(vec![
    ":step".bright_yellow().to_string(),
    "".bright_yellow().to_string(),
    "[#step-index] [step-count]".to_string(),
    "Iterate the step-index of the plan step-count times.".to_string()
  ]);
  builder.push_record(vec![
    ":symbols".bright_yellow().to_string(),
    ":s".bright_yellow().to_string(),
    "[search-pattern]".to_string(),
    "Search symbol directory. If no pattern is provided, the entire directory is printed.".to_string()
  ]);
  builder.push_record(vec![
    ":version".bright_yellow().to_string(),
    ":v".bright_yellow().to_string(),
    "".to_string(),
    "Print version information.".to_string()
  ]);
  builder.push_record(vec![
    ":whos".bright_yellow().to_string(),
    ":w".bright_yellow().to_string(),
    "[search pattern]".to_string(),
    "Print summary table of session variables. Supply a search pattern to filter the table.".to_string()
  ]);
  let mut table = builder.build();
  table.with(mech_table_style())
       .with(Panel::header(format!("{}","Help".yellow())));
  format!("\n{table}\n")
}

pub fn save_to_file(mut path: PathBuf, content: &str) -> MResult<()> {
  // If path is a directory, give it a default file name
  if path.is_dir() {
    path.push("output.html");
  }

  print!("{} Saving file to {}...", "[Save]".truecolor(153,221,85), path.display());
  stdout().flush()?;

  if let Some(parent) = path.parent() {
    fs::create_dir_all(parent)?;
  }

  let mut file = fs::File::create(&path)?;
  file.write_all(content.as_bytes())?;

  println!("Done.");
  Ok(())
}

pub fn ls() -> String {
  let current_dir = env::current_dir().unwrap();
  let mut builder = Builder::default();
  builder.push_record(vec!["Mode","Last Write Time","Length","Name"]);
  for entry in fs::read_dir("./").unwrap() {
    let entry = entry.unwrap();
    let path = entry.path();
    let metadata = fs::metadata(&path).unwrap();
    let file_type = if metadata.is_dir() { "d----" } else { "-a---" };
    let last_write_time = metadata.modified().unwrap();
    let last_write_time: chrono::DateTime<chrono::Local> = last_write_time.into();
    let length = if metadata.is_file() { metadata.len().to_string() } else { "".to_string() };
    let name = format!("{}", path.file_name().unwrap().to_str().unwrap());
    builder.push_record(vec![file_type.to_string(), last_write_time.format("%m/%d/%Y %I:%M %p").to_string(), length, name.to_string()]);
  }
  let mut table = builder.build();
  table.with(mech_table_style())
       .with(Panel::header(format!("{}","Directory Listing".yellow())));
  format!("\nDirectory: {}\n\n{table}\n",current_dir.display())
}

#[cfg(feature = "pretty_print")]
fn pretty_print_tree(tree: &Program) -> String {
  let tree_hash = hash_str(&format!("{:#?}", tree));
  let formatted_tree = tree.pretty_print();
  let mut builder = Builder::default();
  builder.push_record(vec![format!("Hash: {}", tree_hash)]);
  builder.push_record(vec![format!("{}", formatted_tree)]);
  let mut table = builder.build();
  table.with(Style::modern_rounded())
       .with(Panel::header("Syntax Tree"));
  format!("{table}")
}

#[cfg(feature = "whos")]
pub fn whos(intrp: &Interpreter, names: Vec<String>) -> String {
  let mut builder = Builder::default();
  builder.push_record(vec!["Name", "Size", "Bytes", "Kind"]);
  let state = intrp.state.borrow();
  let symbol_table = state.symbol_table.borrow();
  let dictionary = symbol_table.dictionary.borrow();
  if names.is_empty() {
    // Print all symbols
    for (id, value_ref) in symbol_table.symbols.iter() {
      if let Some(name) = dictionary.get(id) {
        let value_brrw = value_ref.borrow();
        builder.push_record(vec![
          name.clone(),
          format!("{:?}", value_brrw.shape()),
          format!("{}", value_brrw.size_of()),
          format!("{}", value_brrw.kind()),
        ]);
      }
    }
  } else {
    // Create a hash set for fast lookup
    let names_set: HashSet<_> = names.iter().collect();
    // Print only requested symbols
    for (id, value_ref) in symbol_table.symbols.iter() {
      if let Some(name) = dictionary.get(id) {
        if names_set.contains(name) {
          let value_brrw = value_ref.borrow();
          builder.push_record(vec![
              name.clone(),
              format!("{:?}", value_brrw.shape()),
              format!("{}", value_brrw.size_of()),
              format!("{}", value_brrw.kind()),
          ]);
        }
      }
    }
  }
  let mut table = builder.build();
  table
    .with(mech_table_style())
    .with(Panel::header(format!(
        "{}",
        "Whos".yellow()
    )));

  format!("\n{table}\n")
}

#[cfg(feature = "pretty_print")]            
fn pretty_print_symbols(intrp: &Interpreter) -> String {
  let mut builder = Builder::default();
  let symbol_table = intrp.pretty_print_symbols();
  builder.push_record(vec![
    format!("{}",symbol_table),
  ]);

  let mut table = builder.build();
  table.with(mech_table_style())   
        .with(Panel::header(format!("{}","Symbols".yellow())));
  format!("\n{table}\n")
}

pub fn clc() {
  let mut stdo = stdout();
  stdo.execute(terminal::Clear(terminal::ClearType::All));
  stdo.execute(cursor::MoveTo(0,0));
}

pub enum Source<'a> {
  UserFile(&'a str),
  Embedded(&'a [u8]),
  Url(&'a str),
}

pub async fn read_or_download(path: &str,backup_url: &str, embedded: Option<&[u8]>) -> MResult<Vec<u8>> {

  // 1. User-supplied path always wins
  match std::fs::read(path) {
    Ok(content) => {
      println!("Using user-supplied resource: {}", path);
      return Ok(content);
    }
    Err(_) => { /* continue to embedded / download */ }
  }

  // 2. Embedded bytes (included via include_bytes!)
  if let Some(bytes) = embedded {
    if !bytes.is_empty() {
      println!("Using embedded resource");
      return Ok(bytes.to_vec());
    }
  }

  // 3. Fallback: Download from remote URL
  println!("Downloading from {}", backup_url);

  let response = reqwest::get(backup_url).await.map_err(|e| {
    MechError::new(
      HttpRequestFailed {
        url: backup_url.to_string(),
        source: e.to_string(),
      },
      None,
    )
    .with_compiler_loc()
  })?;

  if !response.status().is_success() {
    return Err(MechError::new(
      HttpRequestStatusFailed {
        url: backup_url.to_string(),
        status_code: response.status().as_u16(),
      },
      None,
    )
    .with_compiler_loc());
  }

  let bytes = response.bytes().await.map_err(|e| {
    MechError::new(
      HttpRequestFailed {
        url: backup_url.to_string(),
        source: e.to_string(),
      },
      None,
    )
    .with_compiler_loc()
  })?;

  Ok(bytes.to_vec())
}

#[derive(Debug, Clone)]
pub struct HttpRequestFailed {
  pub url: String,
  pub source: String,
}
impl MechErrorKind2 for HttpRequestFailed {
  fn name(&self) -> &str { "HttpRequestFailed" }
  fn message(&self) -> String {
  format!("Failed to GET {}: {}", self.url, self.source)
  }
}

#[derive(Debug, Clone)]
pub struct HttpTextDecodeFailed {
  pub url: String,
  pub source: String,
}
impl MechErrorKind2 for HttpTextDecodeFailed {
  fn name(&self) -> &str { "HttpTextDecodeFailed" }
  fn message(&self) -> String {
  format!("Failed to read response text {}: {}", self.url, self.source)
  }
}

#[derive(Debug, Clone)]
pub struct HttpRequestStatusFailed {
  pub url: String,
  pub status_code: u16,
}
impl MechErrorKind2 for HttpRequestStatusFailed {
  fn name(&self) -> &str { "HttpRequestStatusFailed" }
  fn message(&self) -> String {
  format!("Request to {} failed with status code: {}", self.url, self.status_code)
  }
}

#[derive(Debug, Clone)]
pub struct FileOpenFailed {
  pub file_path: String,
  pub source: String,
}
impl MechErrorKind2 for FileOpenFailed {
  fn name(&self) -> &str { "FileOpenFailed" }

  fn message(&self) -> String {
    format!("Failed to open file {}: {}", self.file_path, self.source)
  }
}

#[derive(Debug, Clone)]
pub struct FileWriteFailed {
  pub file_path: String,
  pub source: String
}
impl MechErrorKind2 for FileWriteFailed {
  fn name(&self) -> &str { "FileWriteFailed" }

  fn message(&self) -> String {
    format!("Failed to write file {}: {}", self.file_path, self.source)
  }
}

#[derive(Debug, Clone)]
pub struct WatchPathFailed {
  pub file_path: String,
  pub source_err: String,
}
impl MechErrorKind2 for WatchPathFailed {
  fn name(&self) -> &str { "WatchPathFailed" }

  fn message(&self) -> String {
    format!("Failed to watch file path {}: {}", self.file_path, self.source_err)
  }
}

#[derive(Debug, Clone)]
pub struct PathNotFound {
  pub file_path: String,
}
impl MechErrorKind2 for PathNotFound {
  fn name(&self) -> &str { "PathNotFound" }

  fn message(&self) -> String {
    format!("Path not found: {}", self.file_path)
  }
}
