# src/mulch/cli.py

import typer
import toml
from pathlib import Path
import logging
from enum import Enum
import datetime
from importlib.metadata import version, PackageNotFoundError
from pprint import pprint

from mulch.decorators import with_logging
from mulch.workspace_manager_generator import WorkspaceManagerGenerator
from mulch.workspace_instance_factory import WorkspaceInstanceFactory
from mulch.logging_setup import setup_logging, setup_logging_portable
from mulch.helpers import dedupe_paths, open_editor, calculate_nowtime_foldername, get_local_appdata_path, get_default_untitled_workspace_name_based_on_operating_system, get_global_config_path, index_to_letters, get_username_from_home_directory
from mulch.commands.dotfolder import create_dot_mulch
from mulch.commands.build_dotmulch_standard_contents import build_dotmulch_standard_contents
from mulch.constants import FALLBACK_SCAFFOLD_TOML, LOCK_FILE_NAME, DEFAULT_SCAFFOLD_FILENAME
from mulch.workspace_status import WorkspaceStatus
from mulch.scaffold_loader import load_scaffold_file, resolve_scaffold
from mulch.reference_lock_manager import ReferenceLockManager, build_flags


logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)


HELP_TEXT = "Mulch CLI for scaffolding Python project workspaces."
SCAFFOLD_TEMPLATES_FILENAME = 'mulch-scaffold-template-dictionary.toml'

FILENAMES_OF_RESPECT = [
    'mulch.toml'
]

# Paths are checked in order of respect for loading the scaffold template dictionary.
# These are used when running `mulch file`, `mulch show`, or any command needing layout templates.
ORDER_OF_RESPECT = [
    Path('.mulch'), # project local hidden folder
    Path.home() / '.mulch', # user profile hidden folde
    get_local_appdata_path("mulch"), # OS standard config path
    get_global_config_path("mulch") # Roaming AppData on Windows, ~/.config on Linux/macOS
]
maybe_global = get_global_config_path(appname="mulch")
if maybe_global:
    ORDER_OF_RESPECT.append(Path(maybe_global))

TEMPLATE_CHOICE_DICTIONARY_FILEPATHS = [
    p / SCAFFOLD_TEMPLATES_FILENAME
    for p in ORDER_OF_RESPECT
    if isinstance(p, Path)
]


try:
    MULCH_VERSION = version("mulch")
    __version__ = version("mulch")
except PackageNotFoundError:
    MULCH_VERSION = "unknown"

try:
    from importlib.metadata import version
    __version__ = version("mulch")
except PackageNotFoundError:
    # fallback if running from source
    try:
        with open(Path(__file__).parent / "VERSION") as f:
            __version__ = f.read().strip()
    except FileNotFoundError:
        __version__ = "dev"
    
# load the fallback_scaffold to this file

# Create the Typer CLI app
app = typer.Typer(help=HELP_TEXT, no_args_is_help=True, add_completion=False)

#@app.callback(invoke_without_command=True)
@app.callback()
def main(
    version: bool = typer.Option(None, "--version", callback=lambda v: print_version(v), is_eager=True, help="Show the version and exit.")
):
    """
    Mulch CLI for scaffolding Python project workspaces
    """
    
    lock = ReferenceLockManager.load_lock()
    if lock is None:
        print("Warning: No reference.lock found — run src and workspace commands first.")
    else:
        pass
        # validate, warn, etc

def print_version(value: bool):
    if value:
        try:
            typer.secho(f"mulch {MULCH_VERSION}",fg=typer.colors.GREEN, bold=True)
        except PackageNotFoundError:
            typer.echo("Version info not found")
        raise typer.Exit()

def _all_order_of_respect_failed(order_of_respect):
    failed = True
    for path in order_of_respect:
        if Path(path).exists():
            failed = False
    return failed

def make_dot_mulch_folder(target_dir):
    return create_dot_mulch(target_dir, order_of_respect=ORDER_OF_RESPECT)

@app.command()
@with_logging
def src(
    target_dir: Path = typer.Option(Path.cwd(), "--target-dir", "-r", help="Target project root (defaults to current directory)."),
    #enforce_mulch_folder: bool = typer.Option(False,"--enforce-mulch-folder-only-no-fallback", "-e", help = "This is leveraged in the CLI call by the context menu Mulch command PS1 to ultimately mean 'If you run Mulch and there is no .mulch folder, one will be generated. If there is one, it will use the default therein.' "),
    stealth: bool = typer.Option(False, "--stealth", "-s", help="Put source files in .mulch/src/ instead of src/."),
    force: bool = typer.Option(False, "--force", help="Override existing, forced."),
    ):
    """
    Build the workspace_manager.py file in the source code, using the mulch.toml structure or the fallback structure embedded in WorkspaceManagerGenerator.
    Establish a logs folder at root, with the logging.json file.
    """

    order_of_respect_local = ORDER_OF_RESPECT
    if _all_order_of_respect_failed(order_of_respect_local):
       make_dot_mulch_folder(target_dir = Path.cwd()) # uses the same logic as the `mulch workspace` command. The `mulch file` command must be run manually, for that behavior to be achieved but otherwise the default is the `.mulch` manifestation. This should contain a query tool to build a `mulch.toml` file is the user is not comfortable doingediting it themselves in a text editor.

    scaffold_data = resolve_scaffold(order_of_respect_local, FILENAMES_OF_RESPECT)
    #pprint(scaffold_data)

    # Create lock data
    lock_data = {
        "scaffold": scaffold_data,
        "generated_by": f"mulch {MULCH_VERSION}",
        "generated_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
        "generated_by": get_username_from_home_directory()
    }
    
    #manager_status = wf.evaluate_manager_status() # check the lock file in src/-packagename-/mulch.lock, which correlates with the workspacemanager
    mgf = WorkspaceManagerGenerator(target_dir, lock_data, stealth=stealth, force=force)
    was_source_generated = mgf.build_src_components()
    if was_source_generated:
        typer.secho(f"📁 Source code created", fg=typer.colors.BRIGHT_GREEN)    
        # create reference lock file contents for src
        flags = build_flags(stealth=stealth)
        ReferenceLockManager.update_lock_src(path=mgf.src_path, flags=flags)

class NamingPattern(str,Enum):
    date = "date"
    new = "new"

def get_folder_name(pattern: NamingPattern = 'date', base_name: str = "New workspace", workspaces_dir: Path = Path.cwd() / "workspaces") -> str:
    '''
    Dynamically generate a workspace folder name based on the chosen pattern.
    Implementation, if the '--name' flag is not used with `mulch workspace`:
     - Default to {date}, and then {date}b, {date}c, {date}d
     - If the '--pattern new' is used when calling `mulch workspace`, the generated name will be 'New workspace', then 'New workspace (2)', etc, if Windows.   
     - 'mulch workspace --pattern new --here' will be used as the default register context menu command for 'mulch workspace', using the mulch-workspace.reg file. 
    '''
    
    if pattern == NamingPattern.date:
        suffix_index = 0
        while True:
            if suffix_index == 0:
                folder_name = calculate_nowtime_foldername()
            else:
                # Skip 'a', start from 'b'
                suffix = index_to_letters(suffix_index + 1)
                folder_name = f"{calculate_nowtime_foldername()}{suffix}"
            if not (workspaces_dir / folder_name).exists(): # 
                return folder_name
            suffix_index += 1
    elif pattern == NamingPattern.new:
        # check for existing workspace folders to append (n) if necessary, like "New workspace (2)", to mimmick windows "New folder (2)" behavior.
        return get_default_untitled_workspace_name_based_on_operating_system(workspaces_dir)
        

@app.command()
@with_logging
def workspace(
    target_dir: Path = typer.Option(Path.cwd(), "--target-dir", "-r", help="Target project root (defaults to current directory)."),
    pattern: NamingPattern = typer.Option(NamingPattern.date, "--pattern", "-p",  help = "Choose naming pattern: 'date' for YYY_MMMMM_DD, or 'new' for os-specific pattern like 'New workspace (n)'"),
    name: str = typer.Option(None, "--name", "-n", help="Name of the workspace to create."),
    here: bool = typer.Option(False, "--here", "-h", help="The new named workspace directory should be placed immediately in the current working directory, rather than nested within a `/workspaces/` directory."),
    set_default: bool = typer.Option(True, "--set-default/--no-set-default", help="Write default-workspace.toml"),
    #enforce_mulch_folder: bool = typer.Option(False,"--enforce-mulch-folder-only-no-fallback", "-e", help = "This is leveraged in the CLI call by the context menu Mulch command PS1 to ultimately mean 'If you run Mulch and there is no .mulch folder, one will be generated. If there is one, it will use the default therein.' "),
    ):
    """
    Initialize a new workspace folder, using the mulch.toml structure or the fallback structure embedded in WorkspaceManagerGenerator.
    """
    # Provide instant feedback on the --here setting.
    if here:
        typer.secho(f"`here`: True.",fg=typer.colors.WHITE)
    
    # First determine workspaces directory
    workspaces_dir = WorkspaceInstanceFactory.determine_workspaces_dir(
        target_dir=target_dir,
        here=here
    )
    # Second determine the value of name. If the name flag was used, use the explicitly provided name.
    # If the name flag was not used, check the value of the pattern flag for which automated name pattern to use.
    if name is None:
        name=get_folder_name(pattern = pattern, workspaces_dir=workspaces_dir)
    
    order_of_respect_local = ORDER_OF_RESPECT
    if _all_order_of_respect_failed(order_of_respect_local):
       make_dot_mulch_folder(target_dir = Path.cwd()) # uses the same logic as the `mulch workspace` command. The `mulch file` command must be run manually, for that behavior to be achieved but otherwise the default is the `.mulch` manifestation. This should contain a query tool to build a `mulch.toml` file is the user is not comfortable doingediting it themselves in a text editor.

    scaffold_data = resolve_scaffold(order_of_respect_local, FILENAMES_OF_RESPECT)
    pprint(scaffold_data)

    # Create lock data
    lock_data = {
        "scaffold": scaffold_data,
        "generated_by": f"mulch {MULCH_VERSION}",
        "generated_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
        "generated_by": get_username_from_home_directory()
    }
    
    logger.debug(f"workspace_dirs = {workspaces_dir}")
    wif = WorkspaceInstanceFactory(target_dir, workspaces_dir, name, lock_data, here=here)
    
    workspace_status = wif.evaluate_workspace_status()
    
    if workspace_status == WorkspaceStatus.MATCHES:
        typer.secho(f"✅ Workspace '{name}' is already set up in {workspaces_dir}", fg=typer.colors.GREEN)
        typer.echo("   (Scaffold unchanged. Nothing regenerated.)")
        raise typer.Exit()

    elif workspace_status == WorkspaceStatus.DIFFERS:
        typer.secho(f"⚠️  Workspace '{name}' already exists and scaffold has changed.", fg=typer.colors.YELLOW)
        if not typer.confirm("Overwrite existing workspace?", default=False):
            typer.secho("❌ Aborting.", fg=typer.colors.RED)
            raise typer.Exit()

    elif workspace_status == WorkspaceStatus.EXISTS_NO_LOCK:
        typer.secho(f"⚠️  Workspace exists at {workspaces_dir / name} but no scaffold.lock found.", fg=typer.colors.YELLOW)
        if not typer.confirm("Overwrite existing workspace?", default=False):
            typer.secho("❌ Aborting.", fg=typer.colors.RED)
            raise typer.Exit()
        
    # Proceed to generate
    wif.create_workspace(set_default=set_default)

    # create reference lock file contents for workspace 
    flags = build_flags(here=here)
    ReferenceLockManager.update_lock_workspace(path=workspaces_dir / name, flags=flags)
    
@app.command()
def context():
    """
    Install the right-click `mulch workspace` context menu registry item.
    """
    from mulch.scripts.install import install_context
    install_context.setup()
    

from rich.table import Table
from rich.console import Console

#@with_logging(use_portable=True)
@app.command()
def order(
    target_dir: Path = typer.Option(Path.cwd(), "--target-dir", "-t", help="Target project root (defaults to current directory)."),
):
    """
    Show the ordered list of mulch scaffold search paths and indicate which exist.
    """
    console = Console()

    table = Table(title="Mulch Scaffold Order of Respect")

    table.add_column("Index", justify="right", style="cyan", no_wrap=True)
    table.add_column("Path", style="magenta")
    table.add_column("Exists?", justify="center", style="green")

    unique_order_of_respect = dedupe_paths(ORDER_OF_RESPECT)

    typer.echo(f"ORDER_OF_RESPECT list:")
    for idx, p in enumerate(unique_order_of_respect):
        typer.echo(f"  {idx+1}: {p} ({p.resolve() if p.exists() else 'does not exist'})")


    for i, path in enumerate(unique_order_of_respect, start=1):
        base_path = (target_dir / path) if not path.is_absolute() else path
        resolved_path = (base_path / "mulch.toml").resolve()
        exists = resolved_path.exists()
        table.add_row(str(i), str(resolved_path), "✅" if exists else "❌")

    console.print(table)
    
def load_template_choice_dictionary_from_file():
    """
    Attempts to load a TOML or JSON template choice dictionary from known fallback paths.
    """
    for path in TEMPLATE_CHOICE_DICTIONARY_FILEPATHS:
        if path.is_file():
            data = load_scaffold_file(path)
            if data is not None:
                typer.secho(f"✅ Loaded template choices from: {path}", fg=typer.colors.GREEN)
                return data
            else:
                typer.secho(f"⚠️ Failed to parse {path.name} as TOML or JSON.", fg=typer.colors.YELLOW)
    typer.secho("❌ Failed to load template choice dictionary from any known paths.", fg=typer.colors.RED)
    raise typer.Exit(code=1)

@app.command()
def seed(#def dotmulch( 
    target_dir: Path = typer.Option(Path.cwd(),"--target-dir","-t", help="Target project root (defaults to current directory)."),
    template_choice: bool = typer.Option(None,"--template-choice","-c",help = "Reference a known template for standing up workspace organization."),
    edit: bool = typer.Option(
        False, "--edit", "-e", help="Open the scaffold file for editing after it's created.")
        ):
    """

    Drop a .mulch folder to disk, at the target directory.
    The default scaffold is the next level of fallback in the ORDER_OF_RESPECT list.
    Edit the .mulch/mulch.toml file manually. Coming soon: interactive prompt file filler.  
    """
    scaffold_dict = resolve_scaffold(ORDER_OF_RESPECT, FILENAMES_OF_RESPECT)
    
    scaffold_path = target_dir / '.mulch' / DEFAULT_SCAFFOLD_FILENAME
    if template_choice:
        typer.secho(f"Choosing scaffold by the template (choose from options)", fg=typer.colors.WHITE)
        template_choice_dict = load_template_choice_dictionary_from_file()
        scaffold_dict = template_choice_dict[template_choice] # template choice must be a number 1-9
    if scaffold_path.exists():
        if not typer.confirm(f"⚠️ {scaffold_path} already exists. Overwrite?"):
            raise typer.Abort()
    scaffold_path.parent.mkdir(parents=True, exist_ok=True)
    with open(scaffold_path, "w", encoding="utf-8") as f:
        toml.dump(scaffold_dict,f)
            
    typer.echo(f"✅ Wrote .mulch to: {scaffold_path}")

    if edit or typer.confirm("📝 Would you like to open the scaffold file for editing now?"):
        open_editor(scaffold_path)

    typer.secho("✏️  You can now manually edit the folder contents to customize your workspace layout and other mulch configuration.",fg=typer.colors.WHITE)
    typer.echo("⚙️  Changes to the scaffold file will directly affect the workspace layout and the generated workspace_manager.py when you run 'mulch src'.")
    
    build_dotmulch_standard_contents(target_dir = Path.cwd())

@app.command()
def show(index: int = typer.Argument(None, help="Index from 'mulch order' to display")):
    """
    Show the scaffold from the first available source in the order of respect,
    or from the specific index if provided.
    """
    # Prepare the order of respect unique and resolved paths
    unique_order_of_respect = dedupe_paths(ORDER_OF_RESPECT)

    # If an index is given, adjust to 0-based and target that path
    if index is not None:
        if index < 1 or index > len(unique_order_of_respect):
            raise typer.Exit(f"Invalid index {index}. Run 'mulch order' to see valid indices.")
        candidate_dir = unique_order_of_respect[index - 1]
        paths_to_check = [candidate_dir]
    else:
        # Otherwise, check in priority order
        paths_to_check = unique_order_of_respect

    for candidate in paths_to_check:
        path = Path(candidate) / "mulch.toml"
        if path.exists():
            typer.echo(f"Loaded scaffold from: {path}")
            if path.suffix.lower() == ".toml":
                
                with open(path, "r", encoding="utf-8") as f:
                    scaffold = toml.load(f)
            else:
                raise typer.Exit(f"Unsupported scaffold file format: {path.suffix}")

            print(toml.dumps(scaffold))
            return

    # Fallback
    scaffold = load_scaffold_file()  # Your embedded fallback
    print("Loaded scaffold from embedded fallback structure.")
    print(toml.dumps(scaffold))


if __name__ == "__main__":
    app()
