#!/bin/bash
cd "$(dirname "$0")" || exit
# Commands such as git pull return exit code if there are no changes
# set -e

if [[ -a ".env" ]]; then
    source .env
fi

help-table() {
    local -a rows
    local max_cmd=7 max_opt=6 max_desc=11

    rows+=("Command|Option|Description")
    rows+=("===ENV===| | ")
    rows+=("copy-dotenv|<env><env>|Copy .env file.")
    rows+=("create-dotenv|<env><type>|Create .env of type nextcloud, odoo, upgrade.")
    rows+=("edit-dotenv|<env>|Open .env file in default editor.")
    rows+=("list-dotenv||List .env files.")
    rows+=("load-dotenv|<env>|Load and export .env file.")
    rows+=("remove-dotenv|<env>|Remove environment config.")
    rows+=("rename-dotenv|<env><env>|Rename .env file.")
    rows+=("restore-dotenv-files|<path>|Extract and copy .env files from backup file.")
    rows+=("show-dotenv|<env>|Output content of the .env file.")
    rows+=("===GIT===| | ")
    rows+=("aggregate-git-folders|<path>|Run gitaggregate for the repos.yaml.")
    rows+=("add-git-folder|<branch><url><path>|Add git folder to .gitmodules.")
    rows+=("checkout|<version>|Checkout Odoo version.")
    rows+=("checkout-git-folder|<branch><path>|Checkout git commit from .gitmodules.")
    rows+=("commit-git-folder|<message><path>|Commit all changes in path.")
    rows+=("clean-git-folder|<path>|Clean git folder.")
    rows+=("clone-git-folder|<branch>|Clone git folders from .gitmodules.")
    rows+=("download-git-folder|<grep>|Download git folders from .gitmodules.")
    rows+=("status-git-folder|<path>|Show status for git folder in path.")
    rows+=("list-git-folder|<path>|List path and url of git folders.")
    rows+=("pull-git-folder|<branch><path>|Pull git folders from .gitmodules.")
    rows+=("push-git-folder|<path>|Push all git folders in path.")
    rows+=("remove-git-folder|<path>|Remove git folders in path.")
    rows+=("ls-git-folder|<grep>|List git folders path space separated.")
    rows+=("reset-git-folder|<path>|Abort rebase and reset git folder.")
    rows+=("stage-git-folder|<path>|Stage all files in git folders in path.")
    rows+=("switch-git-folder|<branch><path>|Switch branch for git folders in .gitmodules.")
    rows+=("sync-git-folder|<branch><path>|Switch, stash and pull all git folders.")
    rows+=("run-pre-commit|<path>|Run pre-commit in path.")
    rows+=("===DOCKER===| | ")
    rows+=("archive-docker-tags||Archive Docker image tags older than 1 year.")
    rows+=("build-odoo|[output]|Build Odoo Docker image.")
    rows+=("build-odoo-mailgate|[output]|Build Odoo Mailgate Docker image.")
    rows+=("build-odoo-upgrade|[output]|Build Odoo Upgrade Docker image.")
    rows+=("build-odoo-cli|[output]|Build Odoo CLI Docker image.")
    rows+=("login-docker|[user][token]|Setup Docker Hub login credentials.")
    rows+=("login-podman|[user][token]|Login into registry with podman.")
    rows+=("update-docker-hub-readme||Update Docker Hub READMEs from image folders.")
    rows+=("container-ps||List container processes.")
    rows+=("remove|[name]|Remove containers and volumes.")
    rows+=("test-image-script|<folder>|Test scripts of target Docker image folder.")
    rows+=("test-odoo-version-regex||Test the Odoo module version regex filter.")
    rows+=("===VUEPRESS===| | ")
    rows+=("build-vuepress||Create Vuepress build.")
    rows+=("install-vuepress||Install Node build dependencies.")
    rows+=("dev-vuepress||Start Vuepress development server.")
    rows+=("serve-vuepress||Serve Vuepress build.")
    rows+=("===DATABASE===| | ")
    rows+=("clear-assets|[db]|Clear all assets of Odoo database.")
    rows+=("clear-filestore|<db>|Clear local filestore folder.")
    rows+=("clear-views|[db]|Clear all views of Odoo database.")
    rows+=("cloc-odoo|[db]|Count custom line of codes.")
    rows+=("drop-db|[db]|Drop target Odoo database.")
    rows+=("init-db|[db]|Initialize the Odoo database.")
    rows+=("load-language|<db>[lang]|Install language package in Odoo db.")
    rows+=("patch-database|<db>[path]|Apply sql file to database.")
    rows+=("reset-views|<db>[key]|Execute hard reset on views matching keys.")
    rows+=("set-admin|[db]|Set password for first user in database.")
    rows+=("update-module|<db>[name,path]|Update target Odoo module.")
    rows+=("update-module-list|<db>|Update module list of Odoo database.")
    rows+=("===REVISION===| | ")
    rows+=("checkout-latest-revision|[version]|Checkout latest revision of Odoo version.")
    rows+=("checkout-revision|[revision]|Load revision env var and checkout folders.")
    rows+=("commit-and-push-revision|[revision]|Commit changes and tag with revision.")
    rows+=("create-revision|<revision>|Create new Odoo revision.")
    rows+=("list-revision||List available Odoo revisions.")
    rows+=("load-latest-revision|[version]|Load the latest revision of the Odoo version.")
    rows+=("load-revision|[revision]|Load env var from specified revision.")
    rows+=("show-revision|[revision]|Show references of Odoo revision.")
    rows+=("===LLM===| | ")
    rows+=("commit-with-llm||Commit with llm generated commit message.")
    rows+=("update-with-llm|<glob><prompt>|Feed files to LLM with prompt and apply changes.")
    rows+=("===SNIPPETS===| | ")
    rows+=("create-snippet|<xml-id>|Create snippet from template.")
    rows+=("install-snippet|<env><path>|Install snippet definition.")
    rows+=("disable-snippet|<env><path>|Disable snippet by path or xml-id.")
    rows+=("enable-snippet|<env><path>|Enable snippet by path or xml-id.")
    rows+=("lint-snippets||Run checks for all snippets.")
    rows+=("remove-snippet|<env><path>|Remove snippet by path or xml-id.")
    rows+=("update-snippet|<env><path>|Update snippet definition.")
    rows+=("===INSTALL===| | ")
    rows+=("activate-venv||Activate virtualenv.")
    rows+=("init-venv||Initialize python virtual env.")
    rows+=("install||Install Odoo requirements in source folder.")
    rows+=("install-ansible-build-scripts|<role>|Install scripts of the specified Ansbile role.")
    rows+=("install-requirements|<db>[path]|Install python packages from requirements.txt.")
    rows+=("list-packages||List installed python packages.")
    rows+=("init-module|<db>[path,module]|Initialize Odoo module.")
    rows+=("set-addons-path||Set Odoo addons path env variable.")
    rows+=("===DATA===| | ")
    rows+=("export-website-data|[env]|Export website data from Odoo database.")
    rows+=("import-csv|<db>[path]|Import csv. Filename is PostgreSQL table.")
    rows+=("import-website-data|[env]|Import website data to Odoo database.")
    rows+=("===DEVELOP===| | ")
    rows+=("create-module|<path>|Create new Odoo module from template.")
    rows+=("create-repo|<path>|Create a new Odoo module repo.")
    rows+=("init-repo-branch|<path>|Create branch for current Odoo version in repo.")
    rows+=("generate-module-docs|<path>|Generate readme file for module with OCA tools.")
    rows+=("generate-module-model|<path><model>|Generate model in module folder.")
    rows+=("generate-module-inherit-model|<path><model>|Generate inherited model in module folder.")
    rows+=("generate-module-views|<path><model>|Generate model views in module folder.")
    rows+=("generate-module-security|<path><model>|Generate model access file.")
    rows+=("generate-module-migration|<path>|Generate migration script for current version.")
    rows+=("generate-module-inherit-view|<path><ref><model>|Generate snippet for referefenced view.")
    rows+=("generate-module-report|<path><model>|Generate report for a model in module folder.")
    rows+=("generate-module-translation|<path>|Generate translation and update with llm.")
    rows+=("generate-module-wizard|<path><model>|Generate wizard for a model in module folder.")
    rows+=("generate-repo-docs|<path>|Generate repo files from template.")
    rows+=("===MODULE===| | ")
    rows+=("list-modules|<path>|Get modules in path as bash array.")
    rows+=("pytest-module|<path>|Run module tests with pytest.")
    rows+=("release-module|<path>|Create GitHub release for a module.")
    rows+=("register-module-repos|[version][path]|Register module repos on apps.odoo.com.")
    rows+=("uninstall-module|<db>[name]|Remove target Odoo module.")
    rows+=("test-module|<path>|Test target Odoo module.")
    rows+=("test-modules|<path>|Test target Odoo modules in repo folder.")
    rows+=("translate-module|<path>[lang][db]|Generate translation for Odoo module.")
    rows+=("upload-module|<env><path>|Zip and upload Odoo module.")
    rows+=("visualize-dependencies|<path>|Generate visualizations of module dependencies.")
    rows+=("zip-module|<path>|Create zip file for module.")
    rows+=("===HELPER===| | ")
    rows+=("generate-admin-password|<pass>|Generate hash for Odoo master password.")
    rows+=("generate-ssh-keys||Generate ssh key pair.")
    rows+=("generate-pg-ssl-keys||Generate PostgreSQL SSL key material.")
    rows+=("get-addons-path||Output addons path.")
    rows+=("get-modules|<path>[option]|List modules in path as 'basename' or 'dirname'.")
    rows+=("get-module-version|<path>|Get module version from manifest.")
    rows+=("get-module-checksum|<path>|Get module checksum.")
    rows+=("help|[grep]|Show help for commands.")
    rows+=("info||Show values of project env vars.")
    rows+=("load-ssh-key||Load SSH private key from env var.")
    rows+=("update-module-license|<grep>|Update LICENSE file for each matching module.")
    rows+=("===RUN===| | ")
    rows+=("list-services||List available services.")
    rows+=("debug|<name>|Debug application.")
    rows+=("exec|<name><cmd>|Run command in container.")
    rows+=("logs|[name]|Tail container logs. Default is '\''odoo'\''.")
    rows+=("manifestoo|[param]|Execute manifestoo cli.")
    rows+=("odoobin|[param]|Execute odoo binary.")
    rows+=("odoocli|[param]|Execute odoocli cli.")
    rows+=("psql|[db]|Start interactive psql shell.")
    rows+=("restart|[name]|Restart container.")
    rows+=("run|<name><cmd>|Run container with command.")
    rows+=("shell|[db][code]|Start interactive odoo shell or run code.")
    rows+=("source||Source the Python virtual env.")
    rows+=("start|[name][db]|Start service.")
    rows+=("stop|[name]|Stop containers.")
    rows+=("test-xmlrpc|<env>|Test xml rpc connection.")
    rows+=("test-json-2|<env>|Test JSON API connection.")
    rows+=("===PROJECT===| | ")
    rows+=("lint||Run precommit for this project.")
    rows+=("list-versions||List available Odoo versions.")
    rows+=("check-version||Check if Odoo source is the correct version.")
    rows+=("load-version|[version]|Load git refs from  version folder.")
    rows+=("save-version||Save git folder refs to version folder.")
    rows+=("template-compose||Template the Docker compose file.")
    rows+=("template-odoo-rc||Template the Odoo config file.")
    rows+=("test-project|[cleanup]|Run tests for this project.")
    rows+=("version||Show version of required tools.")
    rows+=("===PERFORMANCE===| | ")
    rows+=("record-with-memray|<name>|Record application memory usage with memray.")
    rows+=("record-with-py-spy|<pid>|Record and create flamechart for a process.")
    rows+=("===DOC===| | ")
    rows+=("update-docs||Update all project docs.")
    rows+=("update-modules-doc||Update modules docs file.")
    rows+=("update-revisions-doc||Update revisions doc file.")
    rows+=("update-snippets-doc||Update snippets doc file.")
    rows+=("===UPGRADE===| | ")
    rows+=("prepare-migration-brach|<path><module><version>|Create migration branch for a module.")
    rows+=("migrate-module|<path>|Migrate module code to target Odoo version.")
    rows+=("backport-module|<path>|Backport module code to target Odoo version.")
    rows+=("run-openupgrade|<db>|Run OpenUpgrade scripts for database.")
    rows+=("===JUPYTER===| | ")
    rows+=("init-odoo-jupyter|<path>|Init jupyter lab directory.")
    rows+=("start-odoo-jupyter|<path>[db]|Start jupyter lab in directory.")
    rows+=("exec-odoo-jupyter|<path>[db]|Run jupyter notebook with papermill.")
    
    for row in "${rows[@]}"; do
        IFS='|' read -r cmd opt desc <<< "$row"
        (( ${#cmd} > max_cmd )) && max_cmd=${#cmd}
        (( ${#opt} > max_opt )) && max_opt=${#opt}
        (( ${#desc} > max_desc )) && max_desc=${#desc}
    done

    printf '| %-*s | %-*s | %-*s |\n' \
        "$max_cmd" "Command" \
        "$max_opt" "Option" \
        "$max_desc" "Description"

    printf '|-%*s-|-%*s-|-%*s-|\n' \
        "$max_cmd" "$(printf '%*s' "$max_cmd" '' | tr ' ' '-')" \
        "$max_opt" "$(printf '%*s' "$max_opt" '' | tr ' ' '-')" \
        "$max_desc" "$(printf '%*s' "$max_desc" '' | tr ' ' '-')"

    local first_row=true
    for row in "${rows[@]:1}"; do
        IFS='|' read -r cmd opt desc <<< "$row"
        if [[ "$cmd" =~ ^===.*===$ ]]; then
            group_name="${cmd/===/}"
            group_name="${group_name/===/}"
            
            if [ "$first_row" = true ]; then
                first_row=false
            else
                printf '|-%*s-|-%*s-|-%*s-|\n' \
                    "$max_cmd" "$(printf '%*s' "$max_cmd" '' | tr ' ' '-')" \
                    "$max_opt" "$(printf '%*s' "$max_opt" '' | tr ' ' '-')" \
                    "$max_desc" "$(printf '%*s' "$max_desc" '' | tr ' ' '-')"
            fi
            
            local left_pad=$(( (max_cmd - ${#group_name}) / 2 ))
            local right_pad=$(( max_cmd - left_pad - ${#group_name} ))
            printf '| %*s%s%*s | %*s | %*s |\n' \
                "$left_pad" "" \
                "$group_name" \
                "$right_pad" "" \
                "$max_opt" "" \
                "$max_desc" ""
            
            printf '|-%*s-|-%*s-|-%*s-|\n' \
                "$max_cmd" "$(printf '%*s' "$max_cmd" '' | tr ' ' '-')" \
                "$max_opt" "$(printf '%*s' "$max_opt" '' | tr ' ' '-')" \
                "$max_desc" "$(printf '%*s' "$max_desc" '' | tr ' ' '-')"
        else
            first_row=false
            printf '| %-*s | %-*s | %-*s |\n' \
                "$max_cmd" "$cmd" \
                "$max_opt" "$opt" \
                "$max_desc" "$desc"
        fi
    done
}

help() {
    echo
    if [[ -n "$1" ]]; then
        help-table | grep -i "$1" | column -t -s'|'
    else
        echo 'task <command> [options]'
        echo
        echo 'commands:'
        echo
        help-table
    fi
    echo
}


# Static env vars

ODOO_RC="odoo.conf"
export PYTHONPATH="$PWD/odoo" # Required so upgrade-util does not overwrite the odoo namespace
ODOO_VERSION=$(echo "$ODOO_REVISION" | cut -d'.' -f1-2)
CONTAINER_CONFIG="$HOME/.docker/$CONTAINER_REGISTRY"

# Updateable env vars

USER_ORG=${USER_ORG:=Mint-System}
DB_NAME=${DB_NAME:="$ODOO_VERSION"}
WITHOUT_DEMO=${WITHOUT_DEMO:=False}
ODOO_LANGUAGE=${ODOO_LANGUAGE:=de_CH}
ODOO_PORT=${ODOO_PORT:=8069}
NGINX_PORT=${NGINX_PORT:=8080}
JPYTER_LAB_PORT=${JPYTER_LAB_PORT:=8888}
WORKERS=${WORKERS:=0}
POSTGRES_IMAGE=${POSTGRES_IMAGE:=postgres:16-alpine}
POSTGRES_PORT=${POSTGRES_PORT:=5432}
POSTGRES_SSL=${POSTGRES_SSL:=on}
PGSSLMODE=${PGSSLMODE:=prefer}
LOG_LEVEL=${LOG_LEVEL:=info}
CONTAINER_REGISTRY=${CONTAINER_REGISTRY:=mintsystem}
CONTAINER_NETWORK=${CONTAINER_NETWORK:=odoo-build_default}
PLATFORM=${PLATFORM:=linux/amd64}
CONTAINER_ENGINE=${CONTAINER_ENGINE:=docker}
COMPOSE_COMMAND=${COMPOSE_COMMAND:=docker compose}
LLM_MODEL=${LLM_MODEL:=llama}
SMTP_SERVER=${SMTP_SERVER:=localhost}
SMTP_PORT=${SMTP_PORT:=1025}
SMTP_SSL=${SMTP_SSL:=False}
EMAIL_FROM=${EMAIL_FROM:=info@yourcompany.com}
MAIL_ALIAS_DOMAIN=${MAIL_ALIAS_DOMAIN:=yourcompany.com}
LIST_DB=${LIST_DB:=True}
DB_FILTER=${DB_FILTER:=.*}
CONTAINER_TAG="odoo:$ODOO_REVISION"
ODOO_INIT_LOGIN=${ODOO_INIT_LOGIN:=admin}
ODOO_INIT_PASSWORD=${ODOO_INIT_PASSWORD:=admin}
SNIPPET_PREFIX=${SNIPPET_PREFIX:=mint_system}
PROXY_MODE=${PROXY_MODE:=False}

# Conditional env vars

if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
    COMPOSE_COMMAND="podman-compose"
fi

if [[ "$(uname)" == "Darwin" ]]; then
    OS_RELEASE="Darwin"
    PYTHON_NOTIFY=""
else
    OS_RELEASE=$(awk -F= '/^NAME/{print $2}' /etc/os-release | tr -d '"')
    PYTHON_NOTIFY="inotify"
fi

if [[ "$WITHOUT_DEMO" = "True" ]]; then
    ODOO_PARAM="--without-demo=all"
elif [[ "$ODOO_VERSION" = "19.0" ]]; then
    ODOO_PARAM="--with-demo"
fi

# Import commands

clone-taskfile(){
    if [[ ! -d "$HOME/taskfile.build" ]]; then
        echo -e '\033[38;5;214mGit\033[0m: Clone taskfile repo'
        git clone https://git.taskfile.build "$HOME/taskfile.build"
    else
        echo -e '\033[38;5;214mGit\033[0m: Pull taskfile repo'
        git -C "$HOME/taskfile.build" pull
    fi
}

if [[ -d "$HOME/taskfile.build/bin" ]]; then
    for file in "$HOME/taskfile.build/bin/"*; do
        if [[ -f "$file" ]]; then
            source "$file"
        fi
    done
fi

# Help commands

info() {
    set-addons-path
    echo "OS Release: $OS_RELEASE"
    echo "Odoo Revision: $ODOO_REVISION"
    echo "Odoo Version: $ODOO_VERSION"
    echo "Odoo Port: $ODOO_PORT"
    echo "Postgres Port: $POSTGRES_PORT"
    echo "Odoo Language: $ODOO_LANGUAGE"
    echo "Odoo Param: $ODOO_PARAM"
    echo "Container Registry: $CONTAINER_REGISTRY"
    echo "Container Config: $CONTAINER_CONFIG"
    echo "Addons Path: $ADDONS_PATH"
}

version() {
    uv --version
    activate-venv
    python -V
    wkhtmltopdf -V
    if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
        docker -v
        docker compose version
    else
        podman --version
        podman-compose --version
    fi
}

generate-admin-password() {
    if [[ -z "$1" ]]; then echo '\$1/pass is empty.'; exit; fi
    export ODOO_PASSWORD="$1"
    bin/hash-password
}

# Env commands

init-venv() {
    echo "Ensure Python version $(cat .python-version) is installed."
    uv python install

    if [[ ! -d ".venv$ODOO_VERSION" ]]; then
        echo "Init .venv$ODOO_VERSION with $(uv --version)."
        uv venv ".venv$ODOO_VERSION"
    fi
}

activate-venv() {
    if [[ -f  ".venv$ODOO_VERSION/bin/activate" ]]; then
        source ".venv$ODOO_VERSION/bin/activate"
    else
        echo "Could not find .venv$ODOO_VERSION/bin/active. Use init-venv to initalize."
    fi
}

get-addons-path() {
    # Read addons path from git module
    local git_folders_path="$(git config --file .gitmodules --get-regexp path | awk '{ print $2 }' | sort | tr '\n' ',' | sed 's/,*$//')"

    # Remove excluded addons
    local excludes="addons/theme_mint_system addons/company addons/web"
    for exclude in $excludes; do
        git_folders_path=$(echo "$git_folders_path" | sed "s|${exclude},||g; s|^${exclude}$||")
    done

    # Filter valid directories
    local valid_paths=""
    while IFS= read -r addon; do
        addon=$(echo "$addon" | xargs)
        [[ -z "$addon" ]] && continue
        if [[ -d "$addon" ]]; then
            valid_paths="${valid_paths:+$valid_paths,}$addon"
        else
            echo "Set Addons Path: Path '$addon' does not exist." >&2
        fi
    done < <(echo "$git_folders_path" | tr ',' '\n')
    git_folders_path="$valid_paths"

    # Convert to source paths (add /addons where needed)
    local addons_path="$(echo "$git_folders_path" | sed 's|,odoo|,odoo/addons|g')"

    # Append paths from config env var
    if [[ -n "$ODOO_ADDONS_PATH" ]]; then
        addons_path="${ODOO_ADDONS_PATH},${addons_path}"
    fi

    echo "$addons_path"
}

template-odoo-rc() {
    echo "Template $ODOO_RC"

    # Load and export vars defined in odoo conf template
    for var in $(grep -oE '\$([A-Z_][A-Z0-9_]*)|\$\{([A-Z_][A-Z0-9_]*)\}' odoo.conf.template); do
      var=${var/\$/}
      var=${var//\{\//}
      export $var
    done

    local ir_config_parameter=${IR_CONFIG_PARAMETER:-""}

    if [[ -n "$GIT_SSH_PRIVATE_KEY" ]]; then
        local git_ssh_private_key=$(echo -e "$GIT_SSH_PRIVATE_KEY" | base64 -w0)
        ir_config_parameter+=$'\n'"git.ssh_private_key = \"$git_ssh_private_key\""
    fi

    if [[ -n "$GIT_SSH_PUBLIC_KEY" ]]; then
        ir_config_parameter+=$'\n'"git.ssh_public_key = \"$GIT_SSH_PUBLIC_KEY\""
    fi

    if [[ -n "$BASE_URL_WEBSITE" ]]; then
        ir_config_parameter+=$'\n'"base_url_website = \"$BASE_URL_WEBSITE\""
    fi

    if [[ -n "$BASE_URL_APP" ]]; then
        ir_config_parameter+=$'\n'"base_url_app = \"$BASE_URL_APP\""
    fi

    if [[ -n "$UPLOADCARE_PUBLIC_KEY" ]]; then
        ir_config_parameter+=$'\n'"uploadcare.public_key = \"$UPLOADCARE_PUBLIC_KEY\""
    fi

    if [[ -n "$UPLOADCARE_SECRET_KEY" ]]; then
        ir_config_parameter+=$'\n'"uploadcare.secret_key = \"$UPLOADCARE_SECRET_KEY\""
    fi

    if [[ -n "$KEYCLOAK_BASE_URL" ]]; then
        ir_config_parameter+=$'\n'"keycloak.base_url = \"$KEYCLOAK_BASE_URL\""
    fi

    if [[ -n "$KEYCLOAK_CLIENT_ID" ]]; then
        ir_config_parameter+=$'\n'"keycloak.client_id = \"$KEYCLOAK_CLIENT_ID\""
    fi

    if [[ -n "$KEYCLOAK_CLIENT_SECRET" ]]; then
        ir_config_parameter+=$'\n'"keycloak.client_secret = \"$KEYCLOAK_CLIENT_SECRET\""
    fi

    if [[ -n "$KEYCLOAK_REALM" ]]; then
        ir_config_parameter+=$'\n'"keycloak.realm = \"$KEYCLOAK_REALM\""
    fi

    export IR_CONFIG_PARAMETER="$ir_config_parameter"

    envsubst < "odoo.conf.template" > "$ODOO_RC"
}

set-addons-path() {
    export ADDONS_PATH="$(get-addons-path)"
}

load-ssh-key() {
    if [[ -n "$GIT_SSH_PRIVATE_KEY" ]]; then
        echo 'Setup SSH key from env var.'
        local decoded_git_ssh_private_key=$(echo -e "$GIT_SSH_PRIVATE_KEY" | base64 -d)
        mkdir -p ~/.ssh
        echo "$decoded_git_ssh_private_key" > ~/.ssh/id_ed25519
        chmod 600 ~/.ssh/id_ed25519
        eval "$(ssh-agent -s)"
        ssh-add ~/.ssh/id_ed25519 || (echo 'Dumping ~/.ssh/id_ed25519 content:' && cat ~/.ssh/id_ed25519)
    else
        echo 'The SSH key env var is empty.'
        exit 1
    fi
}

# Docs commands

lint() {
    echo "Run pre-commit in $PWD"
    pre-commit run
    echo "Run snippet linting"
    lint-snippets
}

update-snippets-doc() {
    ./bin/update-snippets-doc
}

update-revisions-doc() {

    echo 'Update revisions doc file.'

    rm -f "revisions.md"
    echo -e "# Odoo Revisions \n\
A Odoo revision is a snapshot of git references of the Odoo source and modules at a specific date.\n\
For each major release there are multiple revisions.\n" > "revisions.md"

    declare -A revisions
    local path_url="https://github.com/${USER_ORG}/Odoo-Build/tree/main"

    for file in $(ls revisions/ | sort -rV); do
        local revision="$file"
        local version=$(echo "$revision" | cut -d'.' -f1-2)

        if [[ -z "${revisions[$version]}" ]]; then
            revisions[$version]="## $version"
        fi

        revisions[$version]+=$'\n\n'
        revisions[$version]+="#### $revision"
        revisions[$version]+=$'\n\n```bash\n'
        revisions[$version]+=$(cat "revisions/$file")
        revisions[$version]+=$'\n```'
    done

    for revision in $(echo "${!revisions[@]}" | tr ' ' '\n' | sort -rV); do
        echo -e "${revisions[$revision]}\n" >> "revisions.md"
    done
}

update-modules-doc() {
    echo 'Update module doc file.'
    ./bin/update-modules-doc
}

update-docs() {
    update-modules-doc
    update-revisions-doc
    update-snippets-doc
}

update-module-license() {
    if [[ -z "$1" ]]; then echo '\$1/grep is empty.'; exit; fi
    local git_folders=$(list-module "$1")

    for git_folder in $git_folders; do
       echo "Update license file for $git_folder."
       cp templates/module/LICENSE "$git_folder/"
    done
}

generate-repo-docs(){
    if [[ -z "$1" ]]; then 
        echo '\$1/path is empty.'
        exit
    fi
    local repo_path="$1"

    echo "Copy task file to $repo_path."
    cp "templates/task" "$repo_path"

    echo "Template README.md to $repo_path."
    export REPO_FOLDER=$(basename "$repo_path")
    export REPO_MODEL=${REPO_FOLDER%%_*}
    export REPO_TITLE=$(echo "$REPO_FOLDER" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g')
    export REPO_NAME=$(echo "$REPO_FOLDER" | sed 's/_/-/g' | sed 's/\b\(.\)/\u\1/g')
    envsubst < "templates/README.md" > "$repo_path/README.md"

    echo "Copy $ODOO_VERSION template files to $repo_path."
    cp -r "templates/$ODOO_VERSION/." "$repo_path"

    echo 'Remove deprecated repo files.'
    rm -f "$repo_path/.flake8"
    rm -f "$repo_path/.isort.cfg"
    rm -f "$repo_path/.pylintrc-mandatory"
}

# Module commands

get-modules() {
    if [[ -z "$1" ]]; then echo '\$1/path is empty.'; exit; fi

    local option="$2"
    if [[ -z "$option" ]]; then
        option="dirname"
    fi

    local modules
    if [[ "$option" == "dirname" ]]; then
        modules=$(echo "$1" | tr "," "\n" | xargs -I {} find {} -type f -name "__manifest__.py" | xargs grep -l -iE "version.*([0-9]+\.[0-9]+(\.[0-9]+)?|${ODOO_VERSION}(\.[0-9]+)*)" |  xargs -r dirname | sort -u | tr "\n" "," | sed 's/,$//')
    fi
    if [[ "$option" == "basename" ]]; then
        modules=$(echo "$1" | tr "," "\n" | xargs -I {} find {} -type f -name "__manifest__.py" | xargs grep -l -iE "version.*([0-9]+\.[0-9]+(\.[0-9]+)?|${ODOO_VERSION}(\.[0-9]+)*)" | xargs -r dirname | xargs -r -I {} basename {} | sort -u | tr "\n" "," | sed 's/,$//')
    fi
    echo "$modules"
}

get-module-version() {
    if [[ -z "$1" ]]; then
        echo '\$1/path is empty.'
    else
        # Get version of module
        local version=$(python -c "import ast; print(ast.literal_eval(open('$1/__manifest__.py').read())['version'])")

        # Set default version
        [[ -z "$version" ]] && version=0.0

        local count_dots=$(echo "$version" | grep -o "\." | wc -l)

        # Check if oca version or enterprise version
        if [[ $count_dots == 2 ]]; then
            version="$ODOO_VERSION.$version"
        fi
        if [[ $count_dots == 1 ]]; then
            version="$ODOO_VERSION.$version"
        fi

        echo "$version"
    fi
}

get-module-checksum() {
    if [[ -z "$1" ]]; then
        echo '\$1/path is empty.'
    else
        bin/get-module-checksum "$1"
    fi
}

update-manifest-data() {
    if [[ -z "$1" ]]; then echo '\$1/path is empty.'; exit; fi
    if [[ -z "$2" ]]; then echo '\$2/entry is empty.'; exit; fi
    python -c "
import re, sys
manifest_path = sys.argv[1]
entry = sys.argv[2]
with open(manifest_path, 'r') as f:
    content = f.read()
if ('\"' + entry + '\"') in content:
    print('Entry \"' + entry + '\" already in manifest.')
    sys.exit(0)
match = re.search(r'(\"data\"\s*:\s*\[)([^\]]*?)(\])', content, re.DOTALL)
if not match:
    print('No data list found in manifest.')
    sys.exit(1)
start, inner, end = match.groups()
lines = inner.split('\n')
indent = '        '
for line in lines:
    if line.strip().startswith('\"'):
        indent = line[:len(line) - len(line.lstrip())]
        break
if inner.strip():
    new_inner = inner.rstrip()
    if not new_inner.endswith(','):
        new_inner += ','
    new_inner += '\n' + indent + '\"' + entry + '\"\n    '
else:
    new_inner = '\n' + indent + '\"' + entry + '\"\n    '
new_content = content[:match.start()] + start + new_inner + end + content[match.end():]
with open(manifest_path, 'w') as f:
    f.write(new_content)
print('Updated manifest ' + manifest_path + ' with \"' + entry + '\".')
" "$1" "$2"
}

list-modules() {
    if [[ -z "$1" ]]; then echo '\$1/path is empty.'; exit; fi
    local modules=$(get-modules "$1" dirname)
    echo "$modules" | tr ',' '\n'
}

# Container commands

archive-docker-tags() {
    export CONTAINER_CONFIG
    python bin/archive-docker-tags
}

template-compose() {
    echo 'Template compose.yml'

    # Load and export vars defined in compose template
    for var in $(grep -oE '\$\{?[A-Z_][A-Z0-9_]*\}?' compose.yml.template); do
        # Strip $, ${, }
        var=${var#\$\{}    # Remove leading ${
        var=${var%\}}      # Remove trailing }
        var=${var#\$}      # Remove leading $
        export "$var"
    done

    # Add volume mounts
    VOLUME_MOUNTS=""
    if [[ -d "./tmp/postgres-odoo" ]]; then
        VOLUME_MOUNTS="${VOLUME_MOUNTS}      - ./tmp/postgres-odoo:/mnt/postgres-odoo"$'\n'
    fi
    if [[ -d "./addons" ]]; then
        VOLUME_MOUNTS="${VOLUME_MOUNTS}      - ./addons:/mnt/addons"$'\n'
    fi
    if [[ -d "./oca" ]]; then
        VOLUME_MOUNTS="${VOLUME_MOUNTS}      - ./oca:/mnt/oca"$'\n'
    fi
    if [[ -d "./enterprise" ]]; then
        VOLUME_MOUNTS="${VOLUME_MOUNTS}      - ./enterprise:/mnt/enterprise"$'\n'
    fi
    if [[ -d "./themes" ]]; then
        VOLUME_MOUNTS="${VOLUME_MOUNTS}      - ./themes:/mnt/themes"$'\n'
    fi
    VOLUME_MOUNTS="${VOLUME_MOUNTS%$'\n'}"
    export VOLUME_MOUNTS

    envsubst < "compose.yml.template" > "compose.yml"
}

container-ps() {
    $CONTAINER_ENGINE ps
}

build-odoo() {
    odoo_date=$(echo "$ODOO_REVISION" | cut -d'.' -f3-)
    source "revisions/$ODOO_REVISION"
    manifest_file="images/odoo/Dockerfile"
    container_tag="odoo:$ODOO_VERSION"
    container_revision_tag="odoo:$ODOO_REVISION"

    case "$ODOO_VERSION" in
        "15.0")
            local python_version="3.9"
            PLATFORM="linux/amd64"
            ;;
        "16.0")
            local python_version="3.11"
            ;;
        "17.0")
            local python_version="3.11"
            ;;
        "18.0")
            local python_version="3.12"
            ;;
        "19.0")
            local python_version="3.12"
            ;;
        "20.0")
            local python_version="3.13"
            ;;
    esac

    local docker_output="--load"
    if [[ -n "$1" ]]; then
        docker_output="$1"
    fi

    echo "Python Version: $python_version"
    echo "Platform: $PLATFORM"
    echo "Enterprise Ref: $ODOO_ENTERPRISE_REF"
    echo "Docker Output: $docker_output"

    echo "Remove container image with tag ${CONTAINER_REGISTRY}/${container_revision_tag}"
    docker image rm -f "${CONTAINER_REGISTRY}/${container_revision_tag}"

    if ! docker buildx inspect odoo_builder >/dev/null 2>&1; then
        docker buildx create --name odoo_builder --use
        docker buildx inspect --bootstrap
    else
        docker buildx use odoo_builder
    fi

    echo "Run Docker build ${CONTAINER_REGISTRY}/${container_revision_tag} for ${PLATFORM}"
    SOURCE_DATE_EPOCH=$(git -C odoo log -1 --pretty=%ct) docker buildx build --platform "$PLATFORM" . \
        --file "$manifest_file" \
        --build-arg PYTHON_VERSION="$python_version" \
        --build-arg ODOO_VERSION="$ODOO_VERSION" \
        --build-arg ODOO_DATE="$odoo_date" \
        --build-arg ODOO_ENTERPRISE_REF="$ODOO_ENTERPRISE_REF" \
        --tag "${CONTAINER_REGISTRY}/${container_tag}" \
        --tag "${CONTAINER_REGISTRY}/${container_revision_tag}" \
        "$docker_output"
}

build-odoo-cli() {
    manifest_file="images/odoo-cli/Dockerfile"
    container_tag="odoo-cli:latest"

    local docker_output="--load"
    if [[ -n "$1" ]]; then
        docker_output="$1"
    fi

    echo "Platform: $PLATFORM"
    echo "Docker Output: $docker_output"

    echo "Remove container image with tag ${CONTAINER_REGISTRY}/${container_tag}"
    docker image rm -f "${CONTAINER_REGISTRY}/${container_tag}"

    if ! docker buildx inspect odoo_builder >/dev/null 2>&1; then
        docker buildx create --name odoo_builder --use
        docker buildx inspect --bootstrap
    else
        docker buildx use odoo_builder
    fi

    echo "Run Docker build ${CONTAINER_REGISTRY}/${container_tag} for ${PLATFORM}"
    SOURCE_DATE_EPOCH=$(git -C odoo log -1 --pretty=%ct) docker buildx build --platform "$PLATFORM" . \
        --file "$manifest_file" \
        --tag "${CONTAINER_REGISTRY}/${container_tag}" \
        "$docker_output"
}

build-odoo-mailgate() {
    manifest_file="images/odoo-mailgate/Dockerfile"
    container_tag="odoo-mailgate:latest"

    local docker_output="--load"
    if [[ -n "$1" ]]; then
        docker_output="$1"
    fi

    echo "Platform: $PLATFORM"
    echo "Docker Output: $docker_output"

    echo "Remove container image with tag ${CONTAINER_REGISTRY}/${container_tag}"
    docker image rm -f "${CONTAINER_REGISTRY}/${container_tag}"

    if ! docker buildx inspect odoo_builder >/dev/null 2>&1; then
        docker buildx create --name odoo_builder --use
        docker buildx inspect --bootstrap
    else
        docker buildx use odoo_builder
    fi

    echo "Run Docker build ${CONTAINER_REGISTRY}/${container_tag} for ${PLATFORM}"
    SOURCE_DATE_EPOCH=$(git -C odoo log -1 --pretty=%ct) docker buildx build --platform "$PLATFORM" . \
        --file "$manifest_file" \
        --tag "${CONTAINER_REGISTRY}/${container_tag}" \
        "$docker_output"
}

build-odoo-upgrade() {
    local manifest_file="images/odoo-upgrade/Dockerfile"
    local postgres_versions="14 15 16 17 18"
    local docker_output="--load"

    if [[ -n "$1" ]]; then
        docker_output="$1"
    fi

    if ! docker buildx inspect odoo_builder >/dev/null 2>&1; then
        docker buildx create --name odoo_builder --use
        docker buildx inspect --bootstrap
    else
        docker buildx use odoo_builder
    fi

    for postgres_version in $postgres_versions; do
        container_tag="odoo-upgrade:$postgres_version"

        echo "Posgres Version: $postgres_version"
        echo "Platform: $PLATFORM"
        echo "Docker Output: $docker_output"
        echo "Removing existing image: $full_tag"
        docker image rm -f "$full_tag" 2>/dev/null || true

        echo "Running Docker build for $full_tag on platform $PLATFORM"
        SOURCE_DATE_EPOCH=$(git -C odoo log -1 --pretty=%ct) \
            docker buildx build --platform "$PLATFORM" . \
                --file "$manifest_file" \
                --build-arg POSTGRES_VERSION="$postgres_version" \
                --tag "${CONTAINER_REGISTRY}/${container_tag}" \
                "$docker_output"

        if [[ $? -ne 0 ]]; then
            echo "Build failed for PostgreSQL $postgres_version"
            return 1
        fi
        echo "Build succeeded for PostgreSQL $postgres_version"
    done
}

load-latest-revision() {
    if [[ -n "$1" ]]; then
        ODOO_VERSION="$1"
    fi

    ODOO_REVISION=$(list-revision | grep "$ODOO_VERSION" | tail -n1)
    load-revision "$ODOO_REVISION"
}

checkout-latest-revision() {
    if [[ -n "$1" ]]; then
        ODOO_VERSION="$1"
    fi

    ODOO_REVISION=$(list-revision | grep "$ODOO_VERSION" | tail -n1)
    checkout-revision "$ODOO_REVISION"
    check-version
}

login-podman() {
    if [[ -n "$1" ]]; then
        CONTAINER_REGISTRY_USERNAME="$1"
    fi
    if [[ -n "$2" ]]; then
        CONTAINER_REGISTRY_PASSWORD="$2"
    fi

    echo "Podman login with username $CONTAINER_REGISTRY_USERNAME."
    podman login --username "$CONTAINER_REGISTRY_USERNAME" --password "$CONTAINER_REGISTRY_PASSWORD" docker.io
}

update-docker-hub-readme() {
    bin/update-docker-hub-readme
}

test-odoo-version-regex() {

        # If ODOO_VERSION=19.0 then
        # 1.0 Match
        # 1.1 Match
        # 2.5 Match
        # 2.0.0 Match
        # 13.0.1.0.0 No match
        # 14.0.2.1.1 No match
        # 13.0.1.0.0 No match
        # 19.0.0.0.0 Match

        # Variations of the version key:
        # 'version': '19.0.x',
        # "version": "1.0",
        # 'version': '19.0.1.0.0',

        echo -e "\033[38;5;214mTEST\033[0m: Find and check versions in module"

        versions=$(grep -r --include="__manifest__.py" '[\"'\'']version[\"'\'']:.[\"'\'']' addons enterprise oca odoo themes |
          sed -E 's/.+[\"'\'']version[\"'\'']:.[\"'\'']([0-9].+)[\"'\''].+/\1/g' |
          sort -u)

        ODOO_VERSION="19.0"
        for version in $versions; do
            echo -n "Check version $version: "
            # 1. regex: version.[0-9]\.[0-9]$
            # 2. regex: version.[0-9]\.[0-9]\.[0-9]$
            # 3. regex: version.${ODOO_VERSION}\.[0-9]\.[0-9]\.[0-9]$
            if [[ $version =~ ^[0-9]+\.[0-9]+$ ||
                  $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ||
                  $version =~ ^${ODOO_VERSION}\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
                echo "✓ Match"
            else
                echo "✗ No match"
            fi
        done

        echo -e "\033[38;5;214mTEST\033[0m: Run set-addons-path"
        export PATH=./images/odoo/bin/:$PATH
        export TEST_ADDONS_DIR="./templates,./themes,./enterprise,/gugus,./oca,./addons"
        export ODOO_VERSION="19.0"
        source ./images/odoo/bin/set-addons-path
        echo "ADDONS_PATH: $ADDONS_PATH"
}

test-image-script() {
    # Test the scripts of the Docker images without starting the enviroment.
    set -e

    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi

    if [[ "$1" == "images/odoo" ]]; then
        generate-ssh-keys
        export GIT_SSH_PRIVATE_KEY
        export GIT_SSH_PUBLIC_KEY
        export PATH=./images/odoo/bin/:$PATH
        export LOCAL_PATH="./tmp"
        mkdir -p "$LOCAL_PATH"

        echo -e "\033[38;5;214mTEST\033[0m: Run git clone addons"
        local server_tools_git_ref=$(curl -s "https://api.github.com/repos/OCA/server-tools/branches/$ODOO_VERSION" | grep '"sha":' | head -n 1 | awk -F '"' '{print $4}')
        export ADDONS_GIT_REPOS="https://github.com/OCA/server-tools.git#${ODOO_VERSION}#${server_tools_git_ref}"
        # This will remove all SSH keys at the end
        ./images/odoo/bin/clone-git-addons
        rm -rf "$LOCAL_PATH/github.com" # Cleanup for next run

        echo -e "\033[38;5;214mTEST\033[0m: Install python package."
        export PYTHON_INSTALL="prometheus-client,pyjwt"
        ./images/odoo/bin/install-python-packages

        echo -e "\033[38;5;214mTEST\033[0m: Sync python project."
        cat > tmp/pyproject.toml << 'EOF'
[project]
name = "odoo-build-project"
version = "0.1.0"
description = "Test project for uv sync."
dependencies = [
    "requests>=2.28.0",
    "click>=8.0.0",
]

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[tool.uv]
dev-dependencies = [
    "pytest>=7.0.0",
    "black>=23.0.0",
]
EOF
        ./images/odoo/bin/sync-python-project
        uv pip list


        echo -e "\033[38;5;214mTEST\033[0m: Parse url."
        git_proto=$(./images/odoo/bin/parse-url "git@github.com:OCA/server-tools.git" proto)
        [[ "$git_proto" == "ssh://" ]] && log-success "Test success."
    fi

    if [[ "$1" == "images/odoo-cli" ]]; then
        echo -e "\033[38;5;214mTEST\033[0m: Show help of odoocli."
        docker_odoocli() {
            docker run -it \
                -e ODOO_URL="$ODOO_URL" -e DB_NAME="$DB_NAME" -e ODOO_USERNAME="$ODOO_USERNAME" -e ODOO_PASSWORD="$ODOO_PASSWORD" \
                mintsystem/odoocli "$@"
            }
        docker_odoocli --help
    fi

    if [[ "$1" == "images/odoo-upgrade" ]]; then
        echo -e "\033[38;5;214mTEST\033[0m: Show help of odoo-upgrade."
        docker run -it mintsystem/odoo-upgrade --help
    fi
}

upload-module() {
    if [[ -z "$1" ]]; then
        echo '$1/env is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/path is empty.'
        exit
    fi

    # Check if path is root folder
    local modules="$2"
    if [[ ! -f "$2/__manifest__.py" ]]; then
        echo 'Identified as root folder'
        modules="$modules/*"
    fi

    for module in $modules; do

        if [[ -f "$module/__manifest__.py" ]]; then

            # Create module zip
            zip-module "$module"

            # Get path to zip file and upload url
            local version=$(get-module-version "$module")
            local module_name=$(basename "$module")
            local file_path="tmp/$module_name-$version.zip"
            load-dotenv "$1"
            local nextcloud_upload_url="$NEXTCLOUD_URL/remote.php/dav/files/$NEXTCLOUD_USERNAME/Odoo-Apps/"

            # Upload zip file
            curl -u "$NEXTCLOUD_USERNAME:$NEXTCLOUD_PASSWORD" -T "$file_path" "$nextcloud_upload_url"
            echo "File $file_path uploaded to $nextcloud_upload_url."

            UPLOADS+="  - name: $module_name\n"
            UPLOADS+="    version: $version\n"
        fi
    done

    echo 'odoo_apps:'
    echo -e "$UPLOADS"
}

logs() {
    local container="$1"
    container=${container:="odoo"}
    $CONTAINER_ENGINE logs --follow "$container"
}

# Install commands

install() {
    init-venv
    activate-venv

    if [[ ! -d "$HOME/taskfile.build" ]]; then
        echo -e '\033[38;5;214mGit\033[0m: Clone taskfile repo'
        git clone https://git.taskfile.build "$HOME/taskfile.build"
    else
        echo -e '\033[38;5;214mGit\033[0m: Pull taskfile repo'
        git -C "$HOME/taskfile.build" pull
    fi

    # OS specific installations
    if [[ "$OS_RELEASE" =~ ^(Debian|Debian GNU/Linux|Ubuntu|Pop!_OS)$ ]]; then

        echo -e "\033[38;5;214mApt\033[0m: Update apt repo"
        sudo apt update

        echo -e "\033[38;5;214mApt\033[0m: Install python-ldap and psycopg2 build dependencies"
        sudo apt-get install -y libsasl2-dev libldap2-dev libssl-dev libpq-dev python3-dev

        echo -e "\033[38;5;214mApt\033[0m: Install xmllint and gettext"
        sudo apt install -y libxml2-utils gettext

    elif [[ "$OS_RELEASE" =~ ^(Fedora Linux)$ ]]; then

        echo -e "\033[38;5;214mDnf\033[0m: Update dnf repo"
        sudo dnf update

        echo -e "\033[38;5;214mDnf\033[0m: Install psycopg2 build dependencies"
        sudo dnf install -y postgresql-devel

        echo -e "\033[38;5;214mApt\033[0m: Install gettext"
        sudo dnf install -y gettext

    elif [[ "$OS_RELEASE" =~ ^(Darwin)$ ]]; then

        echo 'Welcome daring Mac user. Let'\''s try our best to get this working.'
        echo -e "\033[38;5;214mBrew\033[0m: Install required packages with brew."
        brew install gettext libsasl2 openldap openssl libxml2 grep

    elif [[ "$OS_RELEASE" =~ ^(Arch Linux|SteamOS)$ ]]; then

        echo 'Arch is the best'

        echo -e "\033[38;5;214mPacman\033[0m: Install python-ldap and psycopg2 build dependencies"
        sudo pacman -S --noconfirm base-devel postgresql-libs

    else
        echo "The operating system $OS_RELEASE is not supported."
        exit 1
    fi

    # Install wkhtmltopdf
    if [[ "$OS_RELEASE" =~ ^(Arch Linux|Darwin|Debian|Debian GNU/Linux|Fedora Linux|Ubuntu|Pop!_OS|SteamOS)$ ]]; then

        # Source: https://gist.github.com/faniska/37f896d5e9de5fee925925d7caf3cb9e
        local installed_version=$(wkhtmltopdf --version 2>&1 | sed -E 's/^wkhtmltopdf +([0-9.]+).*/\1/')
        if [[ "$installed_version" != "0.12.4" ]]; then
            echo -e "\033[38;5;214mCurl\033[0m: Install wkhtmltopdf"
            curl -L -O https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.4/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
            tar xvf wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
            sudo mv wkhtmltox/bin/wkhtmlto* /usr/bin/
            rm -rf wkhtmltox wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
        else
            echo 'wkhtmltopdf version is already 0.12.4'
        fi
    fi

    # Odoo Build requirements
    echo -e "\033[38;5;214mPython\033[0m: Install Odoo Build development tools"
    uv pip install -r requirements.txt

    # Odoo requirements
    if [[ -d "odoo" ]]; then
        echo -e "\033[38;5;214mPython\033[0m: Install Odoo and dependencies."
        cd odoo
        uv pip install -e .
        grep -v "python-ldap" requirements.txt | uv pip install -r -
        cd ..
    fi

    # Install inotify
    echo -e "\033[38;5;214mPython\033[0m: Install $PYTHON_NOTIFY."
    if [[ -n "$PYTHON_NOTIFY" ]]; then
        uv pip install "$PYTHON_NOTIFY"
    fi
    if [[ "$PYTHON_NOTIFY" == "inotify" ]] && ! sudo grep -q "fs.inotify.max_user_watches=524288" /etc/sysctl.d/99-sysctl.conf; then
        sudo bash -c 'echo "fs.inotify.max_user_watches=524288" >> /etc/sysctl.d/99-sysctl.conf'
        sudo sysctl -p /etc/sysctl.d/99-sysctl.conf
        echo -e "\033[38;5;214mSystem\033[0m: Updated fs.inotify.max_user_watches to 524288"
    fi

    # Fixes for Odoo versions
    if [[ "$ODOO_VERSION" == "14.0" ]] ; then
        echo -e "\033[38;5;214mPython\033[0m: Fix shebang for odoo binary."
        sed -i '1s|.*|#!'"$PWD"'/.venv14.0/bin/python|' .venv14.0/bin/odoo
    fi

}

# Process commands

list-services() {
    echo "db"
    echo "mailgate"
    echo "mailpit"
    echo "odoo"
    echo "odoo-nginx"
    echo "source"
}

start() {
    # Generate key material if Postgres SSL is enabled
    if [[ "$POSTGRES_SSL" == "on" ]]; then
        generate-pg-ssl-keys
    fi

    # Prepare Odoo start command
    ODOO_COMMAND="odoo --dev=all"
    if [[ "$1" =~ "odoo-nginx" ]]; then
        ODOO_COMMAND="odoo-nginx --dev=all"
    fi
    export ODOO_COMMAND

    # Load custom env for Odoo container 
    if [[ "$1" =~ "odoo" ]]; then
        export ODOO_ADDONS_PATH=${ODOO_ADDONS_PATH:=/mnt/addons/,/mnt/oca/,/mnt/enterprise,/mnt/themes/}
    fi

    # Use default database if second param is not given
    local database="$2"
    if [[ -z "$2" ]]; then
        database="$DB_NAME"
    fi

    template-compose

    # Start command cases

    if [[ -z "$1" ]]; then
        echo "Start all container ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        echo 'Open http://localhost:8069 url in your browser.'
        $COMPOSE_COMMAND up -d
    fi

    if [[ "$1" =~ "db" ]]; then
        $COMPOSE_COMMAND up -d db
    fi

    if [[ "$1" =~ "mailgate" ]]; then
        echo 'Send emails to: smtp://localhost:587'
        $COMPOSE_COMMAND up -d mailgate
    fi

    if [[ "$1" =~ "mailpit" ]]; then
        echo 'Open http://localhost:8025 url in your browser.'
        open-url "http://localhost:8025"
        $COMPOSE_COMMAND up -d mailpit
    fi

    if [[ "$1" =~ "source" ]]; then
        set-addons-path
        template-odoo-rc
        local port=8069
        while [[ $port -le 8079 ]]; do
            if ! lsof -i:$port >/dev/null 2>&1; then
                break
            fi
            port=$((port + 1))
        done

        echo "Open http://localhost:$port url in your browser."
        open-url "http://localhost:$port/web?debug=1&db=$database"
        odoo --database="$database" --config="$ODOO_RC" \
            --http-port="$port" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
    fi

    if [[ "$1" =~ "odoo" ]]; then
        set-addons-path
        echo "Start Odoo container ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        echo 'Open http://localhost:8069 url in your browser.'
        open-url "http://localhost:8069"
        $COMPOSE_COMMAND up -d odoo
    fi

    if [[ "$1" =~ "odoo-nginx" ]]; then
        set-addons-path
        echo "Start Odoo container ${CONTAINER_REGISTRY}/${CONTAINER_TAG} with nginx proxy."
        echo 'Open http://localhost:8080 url in your browser.'
        open-url "http://localhost:8080"
        $COMPOSE_COMMAND up -d odoo
    fi
}

debug() {
    if [[ -z "$1" ]]; then
        echo '$1/name is empty.'
        exit
    fi
    local database="$2"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi

    set-addons-path

    local port=8069

    if [[ "$1" =~ "source" ]]; then
        # Disable file validation to avoid frozen modules warning
        export PYDEVD_DISABLE_FILE_VALIDATION=1

        echo 'Starting Odoo with the debugger on port 5678.'
        echo "Attach a debugpy debugger and open http://localhost:$port url in your browser."

        python -m debugpy --listen 5678 --wait-for-client $(which odoo) --database="$database" --config="$ODOO_RC" \
            --http-port="$port" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
    fi
}

run() {
    if [[ -z "$1" ]]; then
        echo '$1/name is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/cmd is empty.'
        exit
    fi

    template-compose

    echo "Run '$1' container with command '$2'."
    $COMPOSE_COMMAND run --rm "$1" "$2" "$3"
}

init-odoo-jupyter() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    ./bin/init-odoo-jupyter "$1"
}

start-odoo-jupyter() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local database="$2"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi
    local token="odoo"
    ./bin/start-odoo-jupyter "$1" "$database" "$token"
}

exec-odoo-jupyter() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local database="$2"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi
    
    ./bin/exec-odoo-jupyter "$1" "$database"
}

exec() {
    if [[ -z "$1" ]]; then
        echo '$1/container is empty.'
        exit
    fi
    local container="$1"
    shift
    local cmd="$@"

    echo "Execute command '$cmd' with $CONTAINER_EXEC_PARAM in container '$container'."
    $CONTAINER_ENGINE exec $CONTAINER_EXEC_PARAM "$container" $cmd
}

record-with-py-spy() {
    if [[ -z "$1" ]]; then
        echo '$1/pid is empty.'
        exit
    fi

    local pid="$1"
    local output_file="tmp/speedscope-capture.json"
    local speedscope_url="https://www.speedscope.app/"

    mkdir -p tmp
    echo "source task source; py-spy record --pid $pid --output $output_file --format speedscope" | sudo bash --

    open-with-os "$speedscope_url"
}

record-with-memray() {
    if [[ -z "$1" ]]; then
        echo '$1/name is empty.'
        exit
    fi
    local database="$2"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi
    set-addons-path

    local port=8069
    local output_file="tmp/memray-capture.bin"

    if [[ "$1" =~ "source" ]]; then

        echo 'Starting Odoo with memray.'
        echo "Open http://localhost:$port url in your browser."

        mkdir -p tmp
        rm "$output_file"
        python3 -m memray run -o "$output_file" $(which odoo) --database="$database" --config="$ODOO_RC" \
            --http-port="$port" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
        python3 -m memray flamegraph "$output_file"
        open-with-os "tmp/memray-flamegraph-output.html"
    fi
}

remove() {
    template-compose

    if [[ -z "$1" ]]; then
        $COMPOSE_COMMAND down -v
    fi

    if [[ "$1" =~ "db" ]]; then
        $COMPOSE_COMMAND rm -f -s -v db
    fi

    if [[ "$1" =~ "odoo" ]]; then
        $COMPOSE_COMMAND rm -f -s -v odoo
    fi

    if [[ "$1" =~ "mailpit" ]]; then
        $COMPOSE_COMMAND rm -f -s -v mail
    fi

    if [[ "$1" =~ "mailgate" ]]; then
        $COMPOSE_COMMAND rm -f -s -v mailgate
    fi
}

restart() {
    template-compose

    if [[ -z "$1" ]]; then
        $COMPOSE_COMMAND restart
    fi

    if [[ "db|mail|odoo" =~ $1 ]]; then
        $COMPOSE_COMMAND restart "$1"
    fi
}

stop() {
    template-compose

    if [[ -z "$1" ]]; then
        $COMPOSE_COMMAND stop
    fi

    $COMPOSE_COMMAND stop "$1"
}

generate-ssh-keys() {
    if [[ -z "$GIT_SSH_PRIVATE_KEY" ]]; then
        echo 'Generate SSH private and public keys.'
        mkdir -p tmp
        if [[ ! -f "tmp/ed25519" ]]; then
            ssh-keygen -t ed25519 -C admin@example.com -f tmp/ed25519 -N ''
        fi
        GIT_SSH_PRIVATE_KEY=$(cat tmp/ed25519 | base64 -w0)
        GIT_SSH_PUBLIC_KEY=$(cat tmp/ed25519.pub)
    fi
}

generate-pg-ssl-keys() {
    local keymaterial_path="tmp/postgres-odoo"
    local postgres_uid=70
    local postgres_gid=70
    
    if [[ "$POSTGRES_IMAGE" == "pgvector/pgvector"* ]]; then
        postgres_uid=999
        postgres_gid=999
    fi
    
    if [[ -z "$PG_SSL_CA_CRT" ]] || [[ -z "$PG_SSL_SERVER_CRT" ]] || [[ -z "$PG_SSL_SERVER_KEY" ]]; then
        echo 'Generate PostgreSQL SSL/TLS certificates.'
        mkdir -p "$keymaterial_path"
        
        if [[ ! -f "$keymaterial_path/ca.crt" ]]; then
            openssl genrsa -out "$keymaterial_path/ca.key" 4096
            chmod 600 "$keymaterial_path/ca.key"
            openssl req -x509 -new -nodes -key "$keymaterial_path/ca.key" -sha256 -days 3650 \
                -out "$keymaterial_path/ca.crt" -subj "/C=CH/ST=State/L=Locality/O=Organization/CN=Root CA"
        fi
        
        if [[ ! -f "$keymaterial_path/server.crt" ]]; then
            openssl genrsa -out "$keymaterial_path/server.key" 2048
            openssl req -new -key "$keymaterial_path/server.key" -out "$keymaterial_path/server.csr" \
                -subj "/C=CH/ST=State/L=Locality/O=Organization/CN=localhost"
            openssl x509 -req -in "$keymaterial_path/server.csr" -CA "$keymaterial_path/ca.crt" \
                -CAkey "$keymaterial_path/ca.key" -CAcreateserial -out "$keymaterial_path/server.crt" \
                -days 365 -sha256
        fi
        
        local current_uid=$(stat -c '%u' "$keymaterial_path/server.key" 2>/dev/null || stat -f '%u' "$keymaterial_path/server.key" 2>/dev/null)
        
        if [[ "$current_uid" != "$postgres_uid" ]]; then
            chmod 600 "$keymaterial_path/server.key"
            chmod 644 "$keymaterial_path/server.crt"
            chmod 644 "$keymaterial_path/ca.crt"
            sudo chown "$postgres_uid:$postgres_gid" "$keymaterial_path/server.key" "$keymaterial_path/server.crt" "$keymaterial_path/ca.crt"
        fi
    fi
}

test-project() {
    # This starts the Docker environment and uses internal and external tools to run commands.

    if [[ "$1" == "cleanup" ]]; then
        echo -e "\033[38;5;214mTEST\033[0m: Cleanup test environment"
        drop-db test
        remove odoo
        docker volume rm odoo-build_odoo-data
        git -C oca/partner-contact clean -df
        git -C oca/partner-contact checkout .
    else
        version
        info
        set -e

        echo -e "\033[38;5;214mTEST\033[0m: Clone module repo with https"
        (git clone -b "$ODOO_VERSION" https://github.com/OCA/partner-contact.git oca/partner-contact) || true

        echo -e "\033[38;5;214mTEST\033[0m: Start container environment"
        export BROWSER_OPEN=false
        start db,mail

        echo -e "\033[38;5;214mTEST\033[0m: Clone Odoo addons"
        generate-ssh-keys
        export GIT_SSH_PRIVATE_KEY
        export GIT_SSH_PUBLIC_KEY
        export ADDONS_GIT_REPOS="https://github.com/OCA/server-env.git#${ODOO_VERSION},https://github.com/OCA/server-tools.git#${ODOO_VERSION},https://github.com/Mint-System/Odoo-Apps-Server-Tools.git#${ODOO_VERSION}"
        run odoo clone-git-addons

        echo -e "\033[38;5;214mTEST\033[0m: Init Odoo database"
        export ODOO_ADDONS_PATH="/mnt/oca/partner-contact"
        export DB_NAME=test
        export WITHOUT_DEMO=False
        export ODOO_INIT_LOGIN=test
        export ODOO_INIT_PASSWORD=test
        export ODOO_INIT_LANG=de_CH
        export ODOO_INIT_ADDONS=server_environment_ir_config_parameter,module_auto_update,prometheus_exporter,mail_server_filter,contacts,web_enterprise
        export PYTHON_INSTALL=prometheus-client,manifestoo
        run odoo init-db

        echo -e "\033[38;5;214mTEST\033[0m: Start Odoo container"
        export ODOO_MAIL_SMTP_HOST=mail
        export ODOO_MAIL_SMTP_PORT=1025
        export ODOO_MAIL_SMTP_ENCRYPTION=none
        export SERVER_WIDE_MODULES=module_change_auto_install
        export MODULE_AUTO_INSTALL_DISABLED=odoo_test_xmlrunner
        export AUTO_UPDATE_TRANSLATIONS=True
        export AUTO_UPDATE_MODULES_LIST=True
        export MAIL_DEFAULT_FROM="test"
        export BROWSER_OPEN=false
        export TEST_ADDONS_DIR=/mnt/oca/partner-contact
        export TEST_INCLUDE=partner_firstname
        start odoo
        TIMEOUT=60
        ELAPSED=0
        until curl --silent --fail "http://localhost:8069/web/login" &> /dev/null; do
            if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
                echo "Odoo did not start within $TIMEOUT seconds."
                docker logs odoo
                exit 1
            fi
            echo 'Waiting for Odoo to be ready...'
            sleep 3
            ELAPSED=$((ELAPSED + 3))
        done

        echo -e "\033[38;5;214mTEST\033[0m: Setup requirements.txt file"
        exec odoo bash -c "echo 'click>=8.0.0' > /var/lib/odoo/requirements.txt"
        exec odoo install-python-packages

        # FIXME: Cannot use exec
        click_path=$(docker exec odoo python3 -c 'import click; print(click.__file__)')
        if echo "$click_path" | grep -q "/opt/odoo/venv/"; then
            log-success "click is installed in persisted venv."
        else
            log-error "click is NOT in /opt/odoo/venv/ — found at: $click_path"
            exit 1
        fi

        echo -e "\033[38;5;214mTEST\033[0m: Init repos.yml file"
        cat << EOF > tmp/repos.yml
github.com/Mint-System/Odoo-Apps-Partner-Contact:
    remotes:
        mint_system: https://github.com/Mint-System/Odoo-Apps-Partner-Contact.git
    merges:
        - mint_system ${ODOO_VERSION}
    target: mint_system ${ODOO_VERSION}
EOF
        docker cp tmp/repos.yml odoo:/var/lib/odoo/git/repos.yml
        exec odoo aggregate-git-repos

        echo -e "\033[38;5;214mTEST\033[0m: Init OCA module"
        exec odoo init-module partner_firstname

        echo -e "\033[38;5;214mTEST\033[0m: Setup and check RPC credentials"
        export ODOO_URL="http://localhost:8069"
        export ODOO_DATABASE=test
        export ODOO_USERNAME=test
        export ODOO_PASSWORD=test
        if ! show-dotenv test; then
            create-dotenv test odoo
        fi
        show-dotenv test
        test-xmlrpc test

        echo -e "\033[38;5;214mTEST\033[0m: Install, update, disable and remove snippet"
        install-snippet test snippets/mint_system.base.view_partner_form.show_type.xml
        update-snippet test snippets/mint_system.base.view_partner_form.show_type.xml
        disable-snippet test snippets/mint_system.base.view_partner_form.show_type.xml
        remove-snippet test snippets/mint_system.base.view_partner_form.show_type.xml

        echo -e "\033[38;5;214mTEST\033[0m: Check module auto update"
        sed -i 's/First name/Firstname/g' oca/partner-contact/partner_firstname/models/res_partner.py
        exec odoo update-modules
        # FIXME: Check if module is auto updated
        # docker logs odoo 2>&1 | grep "Run click-odoo-update."

        echo -e "\033[38;5;214mTEST\033[0m: Check translations auto update"
        sed -i 's/Nachname/Familienname/g' oca/partner-contact/partner_firstname/i18n/de.po
        restart odoo
        # FIXME: Check if translation is updated
        # docker logs odoo 2>&1 | grep "Updating translations for all active languages and installed modules."
        # field_description=$(shell test "print(env.ref('partner_firstname.field_res_partner__lastname').field_description)")
        # if [[ "$user_count" != "Familienname" ]]; then
        #     log-error "Expected 'Familienname' field description, but got $field_description"
        #     exit 1
        # fi

        # FIXME: These tests do not work in GitHub Runner env.

        # echo -e "\033[38;5;214mTEST\033[0m: Prepare container for testing."
        # exec odoo setup-tests
        # exec odoo ls /var/lib/odoo/git/github.com/Mint-System/Odoo-Apps-Partner-Contact

        # echo -e "\033[38;5;214mTEST\033[0m: Run module tests."
        # CONTAINER_EXEC_PARAM="-w /mnt/oca/partner-contact"
        # exec odoo run-tests

        echo -e "\033[38;5;214mTEST\033[0m: Run module migration"
        echo "Setup pre migration script for partner_firstname..."
        mkdir -p oca/partner-contact/partner_firstname/migrations/${ODOO_VERSION}.9.0.0
        cat << EOF > oca/partner-contact/partner_firstname/migrations/${ODOO_VERSION}.9.0.0/pre-migrate.py
import logging
from odoo.upgrade import util

_logger = logging.getLogger(__name__)

def migrate(cr, version):
    env = util.env(cr)
    partners = env["res.partner"].search([])
    for partner in partners:
        partner.name += "x"
    _logger.info("Updated %s partners", len(partners))
EOF
        sed -i "/\"version\":/c\    \"version\": \"${ODOO_VERSION}.9.0.0\"," oca/partner-contact/partner_firstname/__manifest__.py
        echo "Execute the module upgrade."
        exec odoo update-module partner_firstname
        # partner_count=$(shell test "print(env['res.partner'].search_count([('name','=','Mitchell Adminx')]))")
        # if [[ "$user_count" != "1" ]]; then
        #     log-error "Expected 1 partner, but got $partner_count"
        #     exit 1
        # fi

        # FIXME: Check if module is migrated
        # docker logs odoo 2>&1 | grep "module partner_firstname: Running migration"

        # echo -e "\033[38;5;214mTEST\033[0m: Drop the test database"
        # drop-db test
    fi
}

# Interactive Shell commands

shell() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi
    local code="$2"

    set-addons-path
    if [[ -n "$code" ]]; then
        echo "$code" | odoo shell --database "$database" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
    else
        odoo shell --database "$database" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
    fi
}

psql() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi

    $CONTAINER_ENGINE exec -it db psql "postgres://odoo:odoo@localhost:5432/$database"
}

# Database commands

init-db() {
    local odoo_init_addons="${ODOO_INIT_ADDONS:-"web"}"
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi

    set-addons-path
    template-odoo-rc

    echo "Initialize database $database with $odoo_init_addons."
    if [[ -n "$ODOO_PARAM" ]]; then
        odoo --database="$database" -i "$odoo_init_addons" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE" "$ODOO_PARAM"
    else
        odoo --database="$database" -i "$odoo_init_addons" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE"
    fi
}

import-csv() {
    if [[ -z "$1" ]]; then
        echo '$1/db is empty.'
        exit
    fi

    local database="$1"
    local file_path="$2"

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$DB_NAME"
        file_path="$1"
    fi

    local file_name=$(basename "$file_path")
    echo "Importing $file_name to ${database}..."
    $CONTAINER_ENGINE cp "$file_path" db:/tmp/
    $CONTAINER_ENGINE exec db psql "postgres://odoo:odoo@localhost:5432/$database" -c "\copy $file_name FROM '/tmp/$file_name' DELIMITER ',' CSV HEADER;"
}

drop-db() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi

    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$database';"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "DROP DATABASE \"$database\";"
}

load-language() {
    if [[ -z "$1" ]]; then
        echo '$1/db is empty.'
        exit
    fi

    local database="$1"
    local language="$2"

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$DB_NAME"
        language="$1"
    fi
    set-addons-path

    echo "Setup language $language for database $database:"
    odoo --database="$database" -u all --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$language"
}

set-admin() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi

    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$database" -c "UPDATE res_users SET active=true,password='\$pbkdf2-sha512\$25000\$JuScEwIg5JzTGqNUivFeqw\$yWTOcix2Afr3XGP2NPY7w4w49e9vpsu14NRndDYXAkbtMF4zkrmx6inVsoLl0zZY30xI/0GzhwonWsK9TUmjWA' WHERE id=2;"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$database" -c "SELECT login, password FROM res_users WHERE id=2;"
}

clear-assets() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi

    echo "Delete these assets for $database:"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "select id,name from ir_attachment where res_model='ir.ui.view' and name like '%assets_%';"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "delete from ir_attachment where res_model='ir.ui.view' and name like '%assets_%';"
}

clear-views() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi

    echo "Archive views with prefix '$SNIPPET_PREFIX."
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "SELECT id,name from ir_ui_view WHERE name like '%$SNIPPET_PREFIX%';"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "UPDATE ir_ui_view SET active=false WHERE name like '%$SNIPPET_PREFIX%';"

    echo 'Archive views with prefix '\''Odoo Studio:'\''.'
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "SELECT id,name from ir_ui_view WHERE name like '%Odoo Studio:%';"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "UPDATE ir_ui_view SET active=false WHERE name like '%Odoo Studio:%';"

    echo "Delete all active views for $database in $CONTAINER :"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "DELETE FROM report_layout WHERE view_id IN (SELECT id FROM ir_ui_view WHERE active = TRUE);"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "WITH RECURSIVE views_to_delete AS (SELECT id, inherit_id FROM ir_ui_view WHERE active = TRUE AND inherit_id IS NULL UNION ALL SELECT v.id, v.inherit_id FROM ir_ui_view v JOIN views_to_delete p ON v.inherit_id = p.id) DELETE FROM ir_ui_view WHERE id IN (SELECT id FROM views_to_delete);"
}

clear-filestore() {
    echo "Remove directory $HOME/.local/share/Odoo/filestore/$1"
    rm -rf "$HOME/.local/share/Odoo/filestore/$1"
}

update-module-list() {
    if [[ -z "$1" ]]; then
        echo '$1/db is empty.'
        exit
    fi

    set-addons-path
    echo "env['ir.module.module'].update_list()" | odoo shell --database "$1" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
}

patch-database() {
    if [[ -z "$1" ]]; then
        echo '$1/db is empty.'
        exit
    fi

    local database="$1"
    local filename="$2"

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$DB_NAME"
        filename="$1"
    fi

    echo "Apply patch $filename to Database $database."
    cat "$filename" | $CONTAINER_ENGINE exec -i db psql "postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost/$database"
    echo 'Patching database succeeded'
}

# Report commands

cloc-odoo() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$DB_NAME"
    fi

    set-addons-path
    odoo cloc --database="$database" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http
}

visualize-dependencies() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi

    mkdir -p tmp
    ./bin/visualize-dependencies "$1"
    open-with-os tmp/odoo-module-dependencies.html
    cat tmp/odoo-module-dependencies.mmd
}

# Setup commands

install-requirements() {
    if [[ -z "$1" ]]; then
        echo '$1/db is empty.'
        exit
    fi

    local database="$1"
    local file_path="$2"

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$DB_NAME"
        file_path="$1"
    fi

    local file_name=$(basename "$file_path")
    if ! [[ "$file_name" =~ .*requirements\.txt$ ]]; then
        echo '$1 must point to requirements.txt file.'
        exit
    fi

    local container_running=$($CONTAINER_ENGINE container inspect -f '{{.State.Running}}' odoo)
    if [[ "$container_running" == "true" ]]; then
        echo 'Installing python packages in container...'
        $CONTAINER_ENGINE exec -u root odoo python -m pip install -r "/mnt/$file_path"
    else
        echo 'Installing python packages in virutal env...'
        uv pip install -r "$file_path"
    fi
}

list-packages() {
    uv pip list
}

# Module commands

init-module() {
    if [[ -z "$1" ]]; then
        echo '$1/db is empty.'
        exit
    fi

    local database="$1"
    local module_path="$2"

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$DB_NAME"
        module_path="$1"
    fi

    # Check if module path is a module repo
    if [[ -f "$module_path/README.md" ]]; then

        # Look for manifest files in repo folder
        # Get parent folder name of manifest files
        # Create a comma separated string of folder names
        local modules=$(get-modules "$module_path" basename)
    else
        local modules=$(basename "$module_path")
    fi

    set-addons-path
    echo "Initialize module $modules on ${database}..."
    odoo --database="$database" -i "$modules" --config="$ODOO_RC" \
        --addons-path="$ADDONS_PATH" --stop-after-init --no-http \
        --log-level="$LOG_LEVEL"
}

update-module() {
    if [[ -z "$1" ]]; then
        echo '$1/db is empty.'
        exit
    fi

    local database="$1"
    local module_name=$(basename "$2")

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$DB_NAME"
        module_name=$(basename "$1")
    fi

    set-addons-path
    echo "Updating module $module_name on ${database}..."
    if [[ -n "$ODOO_PARAM" ]]; then
        odoo --database "$database" --update "$module_name" \
            --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http "$ODOO_PARAM"
    else
        odoo --database "$database" --update "$module_name" \
            --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http
    fi
}

translate-module() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi

    local module_path="$1"
    local module_name=$(basename "$1")
    local language="$2"
    local database="$3"

    if [[ -z "$2" ]]; then
        language="$ODOO_LANGUAGE"
    fi

    if [[ -z "$3" ]]; then
        database="$DB_NAME"
    fi

    set-addons-path
    echo "Generate $language tranlsation file for $module_name on ${database}..."
    mkdir -p "$module_path/i18n/"
    if [[ "$ODOO_VERSION" == "19.0" ]]; then
        odoo --addons-path="$ADDONS_PATH" i18n export "$module_name" --languages "$language" -d "$database" -c "$ODOO_RC"
    else
        odoo --database="$database" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --modules "$module_name" -l "$language" --i18n-export "$module_path/i18n/$language.po"
    fi

    # If lang is de_CH then rename the file to de.po.
    if [[ "$language" == "de_CH" ]]; then
        mv "$module_path/i18n/$language.po" "$module_path/i18n/de.po"
    fi
}

test-module() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local module=$(basename "$1")

    set-addons-path
    echo "Testing module $module..."
    odoo -d "$DB_NAME" -u "$module" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-enable
}

test-modules() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local modules=$(get-modules "$1" basename)
    set-addons-path

    echo "Collect requirements.txt files in $1."
    find "$1" -type f -name "requirements.txt" -exec uv pip install -r {} \;

    echo "Collect test-requirements.txt files in $1."
    find "$1" -type f -name "test-requirements.txt" -exec uv pip install -r {} \;

    depends_modules=$(manifestoo --select-addons-dir "$1" list-depends --separator=,)
    echo "Setup test database with "$ODOO_LANGUAGE" lang and depends modules: $depends_modules"
    odoo -d "$DB_NAME" -i "$depends_modules" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE"

    echo "Testing modules: $modules"
    odoo -d "$DB_NAME" -i "$modules" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-enable
}

pytest-module() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local module_path="$1"

    echo "Setup custom tmp/odoo.conf for pytest."
    set-addons-path
    local odoo_config="tmp/odoo.conf"
    echo "[options]" > "$odoo_config"
    echo "addons_path=$ADDONS_PATH" >> "$odoo_config"
    echo "db_host = localhost" >> "$odoo_config"
    echo "db_password = odoo" >> "$odoo_config"
    echo "db_user = odoo" >> "$odoo_config"

    echo "Run pytest with odoo-http option..."
    pytest -s --odoo-http --odoo-config="$odoo_config" --odoo-database="$DB_NAME" "$module_path"
}

uninstall-module() {
    if [[ -z "$1" ]]; then
        echo '$1/db is empty.'
        exit
    fi

    local database="$1"
    local module_name=$(basename "$2")

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$DB_NAME"
        module_name=$(basename "$1")
    fi

    set-addons-path
    echo "Remove module $module_name"
    echo "self.env['ir.module.module'].search([('name', '=', '$module_name')]).button_immediate_uninstall()" |
    odoo shell --database "$database" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
}

create-module() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi

    local module_path="$1"
    local module_name=$(basename "$module_path")
    local parent_dir=$(dirname "$module_path")

    echo "Scaffolding module $module_name in ${parent_dir}..."
    odoo scaffold "$module_name" "$parent_dir" -t templates/module

    echo 'Prompt for module manifest values...'
    export MODULE_TITLE=$(echo "$module_name" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g')

    read -p "Summary: " MODULE_SUMMARY
    export MODULE_SUMMARY

    while true; do
        read -p "License (AGPL-3/OPL-1): " MODULE_LICENSE
        [[ "$MODULE_LICENSE" == "AGPL-3" || "$MODULE_LICENSE" == "OPL-1" ]] && break
        echo "Invalid. Use AGPL-3 or OPL-1."
    done
    export MODULE_LICENSE

    read -p "Depends (comma-separated and quoted): " MODULE_DEPENDS
    export MODULE_DEPENDS

    while true; do
        read -p "Data? (y/n): " DATA_RESPONSE
        if [[ "$DATA_RESPONSE" == "y" ]]; then
            export MODULE_DATA_KEY='"data": ["data/data.xml"],'
            mkdir -p "$module_path/data"
            cp templates/data.xml "$module_path/data/data.xml"
            break
        elif [[ "$DATA_RESPONSE" == "n" ]]; then
            export MODULE_DATA_KEY='"data": [],'
            break
        else
            echo "Answer y or n."
        fi
    done

    while true; do
        read -p "Demo? (y/n): " DEMO_RESPONSE
        if [[ "$DEMO_RESPONSE" == "y" ]]; then
            export MODULE_DEMO_KEY='"demo": ["demo/demo.xml"],'
            mkdir -p "$module_path/demo"
            cp templates/demo.xml "$module_path/demo/demo.xml"
            break
        elif [[ "$DEMO_RESPONSE" == "n" ]]; then
            export MODULE_DEMO_KEY=""
            break
        else
            echo "Answer y or n."
        fi
    done

    while true; do
        read -p "Test instructions? (y/n): " TEST_RESPONSE
        if [[ "$TEST_RESPONSE" == "y" ]]; then
            mkdir -p "$module_path/tests"
            touch "$module_path/tests/TEST_INSTRUCTIONS.rst"
            break
        elif [[ "$TEST_RESPONSE" == "n" ]]; then
            break
        else
            echo "Answer y or n."
        fi
    done

    while true; do
        read -p "Security? (y/n): " TEST_RESPONSE
        if [[ "$TEST_RESPONSE" == "y" ]]; then
            export MODULE_DATA_KEY='"data": ["security/security.xml"],'
            mkdir -p "$module_path/security"
            cp templates/security.xml "$module_path/security/security.xml"
            break
        elif [[ "$TEST_RESPONSE" == "n" ]]; then
            export MODULE_DATA_KEY='"data": [],'
            break
        else
            echo "Answer y or n."
        fi
    done

    export ODOO_VERSION
    envsubst < templates/module/__manifest__.py > "$module_path/__manifest__.py"
    echo "Created the module $module_path"

    generate-module-docs "$module_path"
}

generate-module-docs() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local module_path="$1"
    local manifest_path="$module_path/__manifest__.py"

    echo 'Setup readme folder'
    mkdir -p "$module_path/readme"
    cp -n -r "templates/module/readme" "$module_path"

    echo 'Update DESCRIPTION.md'
    export MODULE_SUMMARY=$(python3 -c "import ast; d=ast.literal_eval(open('$manifest_path').read()); print(d.get('summary', '').replace('\n', ' ').strip())")
    envsubst < templates/module/readme/DESCRIPTION.md > "$module_path/readme/DESCRIPTION.md"

    echo 'Update CONTRIBUTORS.md'
    export GIT_USER_NAME="$(git config user.name)"
    export GIT_USER_EMAIL="$(git config user.email)"
    envsubst < templates/module/readme/CONTRIBUTORS.md > "$module_path/readme/CONTRIBUTORS.md"

    echo 'Copy icon.png'
    cp templates/module/static/description/icon.png "$module_path/static/description/icon.png"

    echo 'Copy LICENSE'
    cp templates/module/LICENSE "$module_path/"

    echo 'Touch __init__.py'
    touch "$module_path/__init__.py"

    local repo_url=$(git -C "$(dirname $module_path)" remote get-url origin)
    local repo_name=$(basename "${repo_url%.git}")
    echo "Generate OCA README.rst"
    oca-gen-addon-readme --org-name=$USER_ORG --repo-name=$repo_name --branch=$ODOO_VERSION  --addon-dir=$module_path
}

generate-module-migration() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local module_path="$1"
    local module_version=$(get-module-version $module_path)

    echo "Create migrations folder for version $module_version."
    mkdir -p "$module_path/migrations/$module_version"

    echo "Copy pre-migrate template."
    cp "templates/migrations/pre-migrate.py" "$module_path/migrations/$module_version" 
}

generate-module-security() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/model is empty.'
        exit
    fi

    local module_name=$(basename "$1")
    local parent_dir=$(dirname "$1")
    local model_dot_name="$2"
    local model_name=$(echo "$model_dot_name" | tr '.' '_')

    local access_file="$parent_dir/$module_name/security/ir.model.access.csv"
    mkdir -p "$parent_dir/$module_name/security"

    if [[ ! -f "$access_file" ]]; then
        echo "Init security file: $access_file"
        echo "id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink" > "$access_file"
    fi

    # id
    # name
    # model_id:id
    # group_id:id
    # perm_read
    # perm_write
    # perm_create
    # perm_unlink

    echo "Add model access to $access_file"
    echo "access_${model_name}_user,access.$model_dot_name.user,model_${model_name},base.group_user,1,0,0,0" >> "$access_file"
    echo "access_${model_name}_manager,access.$model_dot_name.manager,model_${model_name},base.group_erp_manager,1,1,1,1" >> "$access_file"

    update-manifest-data "$1/__manifest__.py" "security/ir.model.access.csv"
}

create-repo() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local repo_path="$1"
    local repo_folder=$(basename "$repo_path")
    local repo_name=$(echo "$repo_folder" | sed 's/_/-/g' | sed 's/\b\(.\)/\u\1/g')

    echo "Create folder $repo_path."
    mkdir -p "$repo_path"

    generate-repo-docs "$repo_path"

    echo "Init git with branch $ODOO_VERSION"
    git -C "$repo_path" init --initial-branch "$ODOO_VERSION"

    local git_url="git@github.com:${USER_ORG}/Odoo-Apps-$repo_name"
    echo "Add git remote: $git_url "
    git -C "$repo_path" remote add origin "$git_url"

    echo -e "Use this command to register as git folder:\n"
    echo -e "\ttask add-git-folder $git_url $ODOO_VERSION $repo_path\n"
}

init-repo-branch() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local repo_path="$1"
    local repo_name=$(basename "$repo_path")

    echo "Create new branch $ODOO_VERSION in $repo_path"
    git -C "$repo_path" checkout --orphan="$ODOO_VERSION"

    echo "Remove all files from $repo_path"
    rm -rf "$repo_path/"*

    generate-repo-docs "$repo_path"

    local git_url="git@github.com:${USER_ORG}/Odoo-Apps-$repo_name"
    echo -e "Use this command to register as git folder:\n"
    echo -e "\ttask add-git-folder $git_url $ODOO_VERSION $repo_path\n"
}

export-module-vars() {
    export MODULE_NAME=$(basename "$1") # partner_email_history
    export MODEL_DOT_NAME="$2" # res.partner
    export MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner
    export NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2  | sed 's/[^_]\+/\L\u&/g') # Partners
    export MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner
    export MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner
}

export-module-inherit-vars() {
    export MODEL_DOT_NAME="$3" # sale.order
    export VIEW_REF="$2" # sale.view_order_form
    export MODEL_NAME=${MODEL_DOT_NAME//\./_} # sale_order
    export MODULE_NAME=$(basename "$1") # sale_helm
    export SRC_MODULE_NAME=$(echo "$VIEW_REF" | cut -d"." -f1) # sale
    export XML_ID=$(echo "$VIEW_REF" | cut -d"." -f2) # view_order_form
}

generate-module-model() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/model is empty.'
        exit
    fi
    local module_path="$1"

    echo 'Substitute model template'
    export-module-vars "$1" "$2"
    envsubst < templates/model.py > "$module_path/models/$MODEL_NAME.py"
    echo "from . import $MODEL_NAME" >> "$module_path/models/__init__.py"
    echo "File "$module_path/models/$MODEL_NAME.py created.
}

generate-module-wizard() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/model is empty.'
        exit
    fi
    local module_path="$1"

    echo 'Create wizard folder and init file'
    mkdir -p "$module_path/wizard"
    touch "$module_path/wizard/__init__.py"

    echo 'Substitute wizard template'
    export-module-vars "$1" "$2"
    envsubst < templates/wizard.py > "$module_path/wizard/$MODEL_NAME.py"
    echo "from . import $MODEL_NAME" >> "$module_path/wizard/__init__.py"
    echo "File "$module_path/wizard/$MODEL_NAME.py created.
}

generate-module-translation() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local module_path="$1"
    local module_name=$(basename "$1")

    echo "Translate module $module_name."
    translate-module "$module_path"

    echo "Translate the de.po with llm..."
    task update-with-llm "$module_path/i18n/de.po" "Add missing translations."
}

generate-module-inherit-model() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/model is empty.'
        exit
    fi
    local module_path="$1"
    local module_name=$(basename "$1")

    echo 'Substitute model template'
    export-module-vars "$1" "$2"
    envsubst < templates/inherit.py > "$module_path/models/$MODEL_NAME.py"
    echo "from . import $MODEL_NAME" >> "$module_path/models/__init__.py"
    echo "File "$module_path/models/$MODEL_NAME.py created.
}

generate-module-views() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/model is empty.'
        exit
    fi
    local module_path="$1"
    local module_name=$(basename "$1")

    echo 'Substitute view template'
    mkdir -p "$module_path/views"
    export-module-vars "$1" "$2"
    envsubst < templates/view.xml > "$module_path/views/${MODEL_NAME}.xml"
    update-manifest-data "$module_path/__manifest__.py" "views/${MODEL_NAME}.xml"
}

generate-module-inherit-view() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/ref is empty.'
        exit
    fi
    if [[ -z "$3" ]]; then
        echo '$3/model is empty.'
        exit
    fi
    local module_path="$1"

    echo 'Substitute inherit template'
    mkdir -p "$module_path/views"
    export-module-inherit-vars "$1" "$2" "$3" # addons/sale_workflow/sale_helm sale.view_order_form sale.order
    envsubst < templates/inherit.xml > "$module_path/views/${MODEL_NAME}.xml"
    update-manifest-data "$module_path/__manifest__.py" "views/${MODEL_NAME}.xml"
}

generate-module-report() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/model is empty.'
        exit
    fi
    local module_path="$1"
    local module_name=$(basename "$1")

    echo 'Substitute view template'
    mkdir -p "$module_path/report"
    export-module-vars "$1" "$2"
    envsubst < templates/template.xml > "$module_path/report/${MODEL_NAME}.xml"
    update-manifest-data "$module_path/__manifest__.py" "report/${MODEL_NAME}.xml"
}

release-module() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi

    local version=$(get-module-version "$1")
    cd "$1" || exit
    local last_tag=$(git describe --tags)
    local release_notes=$(git log "$last_tag"..HEAD --oneline)

    echo "Tag with v${version}..."
    git tag -a "v$version" -m "v$version"

    echo 'Release notes:'
    echo "$release_notes"
}

zip-module() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi

    # Get version and module name
    local version=$(get-module-version "$1")
    local module=$(basename "$1")

    # Copy module to tmp folder
    echo "Copy module $module version $version to tmp folder"
    local module_name="$module-$version"
    local module_path="tmp/$module_name"
    rm -rf "$module_path"
    mkdir -p "$module_path"
    cp -r "$1/." "$module_path"
    find "$module_path" -type d -exec chmod u=rwx,go=rx {} \;
    find "$module_path" -type f -exec chmod u=rw,go=r {} \;

    # Configure and zip module
    echo 'Remove auto install option'
    sed -i'' -e "s/'auto_install': True,/'auto_install': False,/" "$module_path/__manifest__.py"
    echo 'Remove pycache and hidden folders'
    find "$module_path" | grep -E "(\.git|__pycache__|\.pyc|\.pyo$)" | xargs rm -rf
    echo "Zip module $module"
    cd tmp || exit
    rm -f "$module_name".zip
    zip -q -r "$module_name".zip "$module_name"
    cd .. || exit
}

register-module-repos() {
    local version="${1:-$ODOO_VERSION}"
    local path="${2:-addons}"

    if [[ -z "$version" ]]; then
        echo 'Error: ODOO_VERSION not set and no version provided.'
        exit 1
    fi

    echo "Registering module repos for Odoo version: $version"
    echo "Path: $path"
    echo ""

    ./bin/register-module-repos "$path" "$version"
}

# Upgrade commands

prepare-migration-brach() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/module is empty.'
        exit
    fi
    if [[ -z "$3" ]]; then
        echo '$3/version is empty.'
        exit
    fi
    local repo="$1"
    local module="$2"
    local version="$3"
    
    echo "Create migration branch for $module module in $repo for version $ODOO_VERSION"
    git -C "$repo" checkout -b "$ODOO_VERSION-mig-$module" "origin/$ODOO_VERSION"

    echo "Checkout $module from $version"
    git -C "$repo" checkout "$version" "$module"

    echo "Create patch file from version $version to $ODOO_VERSION"
    git format-patch --keep-subject --stdout origin/$ODOO_VERSION..origin/$version -- $module | git am -3 --keep

    echo "Run pre-commit"
    cd "$repo"
    pre-commit run -a

    echo "Commit the changes"
    git add --all
    git commit -m "[IMP] $module: pre-commit auto fixes"  --no-verify

    echo "Add git remote for OCA"
    git remote add oca $(git remote get-url origin)

    echo "Add git remote for $USER_ORG"
    local user_org="${USER_ORG,,}"
    local repo_name=$(echo "$repo" | sed 's/_/-/g' | sed 's/\b\(.\)/\u\1/g')
    git remote add "$user_org" "https://github.com/${USER_ORG}/$repo_name"
}

migrate-module() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local module_path="$1"
    local source_odoo_version=$(get-module-version $module_path | cut -d'.' -f1,2)
    local module=$(basename "$module_path")

    echo "Run odoo-module-migrator for $module with source $source_odoo_version and target ${ODOO_VERSION}..."
    odoo-module-migrate --directory $(dirname "$module_path") --modules $(basename "$module_path") \
        --init-version-name $source_odoo_version --target-version-name $ODOO_VERSION --no-commit
}

backport-module() {
    if [[ -z "$1" ]]; then
        echo '$1/path is empty.'
        exit
    fi
    local module_path="$1"
    local source_odoo_version=$(get-module-version $module_path | cut -d'.' -f1,2)
    local module=$(basename "$module_path")

    echo "Replace manifest version $source_odoo_version with $ODOO_VERSION."
    sed -i -E "s/$source_odoo_version/$ODOO_VERSION/g" "$module_path/__manifest__.py"
}

run-openupgrade() {
    if [[ -z "$1" ]]; then
        echo '$1/database is empty.'
        exit
    fi
    database="$1"

    echo "Run OpenUpgrade for database '$database'..."
    set-addons-path
    template-odoo-rc
    odoo --database="$database" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --update all --stop-after-init --load=base,web,openupgrade_framework
}

# Snippet commands

reset-views() {
    if [[ -z "$1" ]]; then
        echo '$1/db is empty.'
        exit
    fi

    local database="$1"
    local key=$(basename "$2")

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$DB_NAME"
        key=$(basename "$1")
    fi

    set-addons-path
    echo "Reset views for key $key"
    echo "self.env['ir.ui.view'].search([('key', 'ilike', '$key')]).reset_arch(mode='hard')" |
    odoo shell --database "$database" --config="$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
}

create-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1/xml-id is empty.'
        exit
    fi

    export INHERIT_ID=$(echo "$1" | cut -d. -f1-2)
    local file="snippets/$SNIPPET_PREFIX.$1.xml"

    echo "Create snippet from template: $file"
    envsubst '$INHERIT_ID' < templates/snippet.xml > "$file"
}

install-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1/env is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/path is empty.'
        exit
    fi

    # Generate view name from filename
    local file=$(basename "$2")
    local name="${file%.xml}"
    local inherit_id=$(echo "$name" | cut -d. -f 2,3 )
    local module_name=$(echo "$inherit_id" | cut -d. -f 1 )
    local xml_id=$(echo "$inherit_id" | cut -d. -f 2 )

    local type=$(echo 'cat //data/@type' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
    local model=$(echo 'cat //data/@model' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
    local priority=$(echo 'cat //data/@priority' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')

    # Set default values
    : ${type:='qweb'}

    load-dotenv "$1"
    echo "Search for view with name $module_name.$xml_id"
    inherit_id=$(images/odoo-cli/bin/odoocli --method "read" --model "ir.model.data" --field "res_id" --domain "[('name', '=', '$xml_id'), ('module', '=', '$module_name')]")
    echo "Found view id $inherit_id"

    echo "Apply view $name"
    images/odoo-cli/bin/odoocli --method 'create' --model 'ir.ui.view' --value "{
        'name': '$name',
        'type': '$type',
        'model': '$model',
        'mode': 'extension',
        'priority': $priority,
        'inherit_id': $inherit_id,
        'arch_base': '''$(cat "$2")'''
    }"
}

update-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1/env is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/path is empty.'
        exit
    fi

    # Generate view name from filename
    local file=$(basename "$2")
    local name="${file%.xml}"

    load-dotenv "$1"
    echo "Update view with name $name"
    images/odoo-cli/bin/odoocli --method 'write' --model 'ir.ui.view' --include-archived --domain "[('name', '=', '$name')]" --field 'arch_base' --value "'''$(cat "$2")'''"
    enable-snippet "$1" "$2"
}

disable-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1/env is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/path is empty.'
        exit
    fi

    # Get name from path
    # path: snippets/mint_system.base.view_partner_tree.show_type.xml
    # name: mint_system.base.view_partner_tree.show_type
    local name="$2"
    if [[ "$2" == *.xml ]]; then
        local file=$(basename "$2")
        name="${file%.xml}"
    fi

    load-dotenv "$1"
    echo "Disable view with name $name"
    images/odoo-cli/bin/odoocli --method 'write' --model "ir.ui.view" --domain "[('name', '=', '$name')]" --field "active" --value "False"
}

enable-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1/env is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/path is empty.'
        exit
    fi

    # Get name from path
    local name="$2"
    if [[ "$2" == *.xml ]]; then
        local file=$(basename "$2")
        name="${file%.xml}"
    fi

    load-dotenv "$1"
    echo "Enable view with name $name"
    images/odoo-cli/bin/odoocli --method 'write' --model "ir.ui.view" --include-archived --domain "[('name', '=', '$name')]" --field "active" --value "True"
}

remove-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1/env is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/path is empty.'
        exit
    fi

    # Get name from path
    local name="$2"
    if [[ "$2" == *.xml ]]; then
        local file=$(basename "$2")
        name="${file%.xml}"
    fi

    load-dotenv "$1"
    echo "Remove view with name $name"
    images/odoo-cli/bin/odoocli --method "unlink" --model "ir.ui.view" --domain "[('name', '=', '$name')]"
}

lint-snippets() {
    echo 'Lint snippets files...'
    ./bin/lint-snippets
}

# Project commands

load-version() {
    if [[ -n "$1" ]]; then
        ODOO_VERSION="$1"
    fi

    echo "Copy .gitmodules and .python-version from 'versions/$ODOO_VERSION'."
    cp "versions/$ODOO_VERSION/.gitmodules" .
    cp "versions/$ODOO_VERSION/.python-version" .
}

checkout() {
    if [[ -z "$1" ]]; then echo "\$1/version is empty."; exit; fi
    ODOO_VERSION="$1"

    # Load .gitmodules and .python-version
    load-version "$ODOO_VERSION"

    # Set .env
    load-revision "$ODOO_VERSION"

    echo "Run clone-git-folder"
    clone-git-folder .

    echo "Run checkout-git-folder"
    checkout-git-folder "$ODOO_VERSION" .

    echo "Run switch-git-folder"
    switch-git-folder "$ODOO_VERSION" .

    # List git folders
    list-git-folder .
}

save-version() {
    save-git-folder .

    echo "Copy .gitmodules and .python-version to 'versions/$ODOO_VERSION'"
    cp ".gitmodules" "versions/$ODOO_VERSION/"
    cp ".python-version" "versions/$ODOO_VERSION/"
    
    echo "Remove .gitmodules.csv."
    rm ".gitmodules.csv"
    rm "versions/$ODOO_VERSION/.gitmodules.csv"
}


list-versions() {
    ls -1 versions
}

check-version() {
    echo 'Check if Odoo version matches the release file:'
    local odoo_version=$(echo "$ODOO_VERSION" | cut -d'.' -f1)
    grep -E "version_info.+$odoo_version" odoo/odoo/release.py
}

# Revision commands

list-revision() {
    ls -1 revisions
}

show-revision() {
    if [[ -n "$1" ]]; then
        ODOO_REVISION="$1"
    fi

    echo "Odoo Revision: $ODOO_REVISION"
    source "revisions/$ODOO_REVISION"

    echo "Odoo Ref: $ODOO_REF"
    echo "Odoo Enterprise Ref: $ODOO_ENTERPRISE_REF"
    echo "Odoo Themes Ref: $ODOO_THEME_REF"
}

load-revision() {
    if [[ -n "$1" ]]; then
        ODOO_REVISION="$1"
    fi

    echo 'Update revision var in .env file.'
    if grep -q "ODOO_REVISION" .env; then
        sed -i -e "s/^ODOO_REVISION=.*/ODOO_REVISION=${ODOO_REVISION}/" .env
    else
        echo "ODOO_REVISION=${ODOO_REVISION}" >> .env
    fi

    echo "Load refs for revision: $ODOO_REVISION"
    export $(cat "revisions/$ODOO_REVISION" | sed 's/#.*//g' | xargs)
}

checkout-revision() {
    if [[ -n "$1" ]]; then
        ODOO_REVISION="$1"
    fi

    load-revision "$ODOO_REVISION"

    echo 'Checkout Odoo git repos...'
    git -C odoo checkout $ODOO_REF
    git -C enterprise checkout $ODOO_ENTERPRISE_REF
    git -C themes checkout $ODOO_THEME_REF
}

create-revision() {
    if [[ -z "$1" ]]; then echo "\$1/revision is empty."; exit; fi

    local revision="$1"
    local odoo_date=$(echo "$revision" | cut -d'.' -f3-)
    local before_date=$(date -d "$odoo_date -1 day" +"%Y-%m-%d")
    echo "Checkout Odoo repos on $before_date."

    # Get git commit by date
    cd odoo
    git fetch
    echo -e "\nShow last 3 commits from $before_date:"
    git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$before_date" origin/$ODOO_VERSION
    local ref=$(git rev-list -n 1 --before="$before_date" origin/$ODOO_VERSION)
    git checkout "$ref"
    cd ..

    cd enterprise
    git fetch
    echo -e "\nShow last 3 commits from $before_date:"
    git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$before_date" origin/$ODOO_VERSION
    local enterprise_ref=$(git rev-list -n 1 --before="$before_date" origin/$ODOO_VERSION)
    git checkout "$enterprise_ref"
    cd ..

    cd themes
    git fetch
    echo -e "\nShow last 3 commits from $before_date:"
    git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$before_date" origin/$ODOO_VERSION
    local theme_ref=$(git rev-list -n 1 --before="$before_date" origin/$ODOO_VERSION)
    git checkout "$theme_ref"
    cd ..

    echo -e "\nCreate revision: $revision"
    export ODOO_REVISION=$revision
    export ODOO_REF=$ref
    export ODOO_ENTERPRISE_REF=$enterprise_ref
    export ODOO_THEME_REF=$theme_ref
    envsubst < templates/odoo_revision > "revisions/$revision"
    chmod +x "revisions/$revision"

    echo "Checkout revision: $revision"
    checkout-revision "$revision"
}

commit-and-push-revision() {
    if [[ -n "$1" ]]; then
        ODOO_REVISION="$1"
    fi

    echo 'Remove existing tag'
    git tag -d "$ODOO_REVISION" 2>/dev/null || true

    echo "Commit with revision $ODOO_REVISION tag"
    if ! git diff --quiet HEAD; then
        git commit --all --message "feat(revision): release $ODOO_REVISION"
    else
        echo 'Nothing to commit, continuing...'
    fi

    echo "Tag commit with $ODOO_REVISION"
    git tag -f "$ODOO_REVISION"

    echo 'Push tags...'
    git push
    git push -f --tags
}

# RUN commands

test-xmlrpc() {
    load-dotenv "$1"

    if [[ -z "${ODOO_URL}" ]]; then
        echo "ODOO_URL is not set."
        return 1
    fi
    if [[ -z "${ODOO_DATABASE}" ]]; then
        echo "ODOO_DATABASE is not set."
        return 1
    fi
    if [[ -z "${ODOO_USERNAME}" ]]; then
        echo "ODOO_USERNAME is not set."
        return 1
    fi
    if [[ -z "${ODOO_PASSWORD}" ]]; then
        echo "ODOO_PASSWORD is not set."
        return 1
    fi

    echo "Connect to database and count res.partner records: $(images/odoo-cli/bin/odoocli --model 'res.partner' --method 'search_count' --domain '[]')"
}

test-json-2() {
    load-dotenv "$1"

    local url="${ODOO_URL%/}/json/2/res.partner/search_count"
    local result

    if [[ -z "${ODOO_API_KEY}" ]]; then
        echo "ODOO_API_KEY is not set."
        echo "Generate an API key in Odoo (User menu > Preferences > Account Security > New API Key)."
        return 1
    fi

    result=$(curl -s -X POST \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer ${ODOO_API_KEY}" \
        -d '{"domain": []}' \
        "$url")

    echo "Connect to database and count res.partner records: $result"
}

odoobin(){
    odoo "$@"
}

odoocli(){
    images/odoo-cli/bin/odoocli "$@"
}

manifestoo(){
    set-addons-path
    ./.venv$ODOO_VERSION/bin/manifestoo --addons-path="$ADDONS_PATH" "$@"
}

export-website-data() {
    if [[ -n "$1" ]]; then
        load-dotenv $1
    fi

    export ODOO_URL
    export DB_NAME
    export ODOO_USERNAME
    export ODOO_PASSWORD
    export ODOO_WEBSITE_ID

    echo -e "\nPython Output:\n\n---"
    python "bin/export-website-data"
    local exit_code=$?
    echo -e "---\n"
    echo -e "Exit code: $exit_code\n"
}

import-website-data() {
    if [[ -n "$1" ]]; then
        load-dotenv $1
    fi

    export ODOO_URL
    export DB_NAME
    export ODOO_USERNAME
    export ODOO_PASSWORD
    export ODOO_WEBSITE_ID

    echo -e "\nPython Output:\n\n---"
    python "bin/import-website-data"
    local exit_code=$?
    echo -e "---\n"
    echo -e "Exit code: $exit_code\n"
}

# Vuepress commands

install-vuepress() {
    pnpm install
}

dev-vuepress() {
    open-url http://localhost:8080
    pnpm run dev
}

build-vuepress() {
    pnpm run build

    echo "Create vuepress snippets folders."
    mkdir -p .vuepress/dist/snippets

    echo "Copy snippet files to snippets folder."
    cp snippets/*.xml .vuepress/dist/snippets
}

serve-vuepress() {
    cd .vuepress/dist
    open-url http://localhost:3000
    pnpx serve
}

run-vuepress-container() {
    container_name="odoo-build"
    image_tag="mintsystem/taskfile.build:latest"

    docker pull "$image_tag"
    docker rm -f "$container_name"

    echo "Open http://localhost:8888"
    docker run -p 8888:80 -d \
        --name "$container_name" \
        -e GIT_URL="https://github.com/Mint-System/Odoo-Build" \
        -e TASK_COMMANDS="install-vuepress,build-vuepress" \
        -e CADDY_ROOT=".vuepress/dist" \
        "$image_tag"
}

if [[ "$1" == *","* ]]; then
    # Split comma-separated commands
    IFS=',' read -ra COMMANDS <<< "$1"
    for cmd in "${COMMANDS[@]}"; do
        # Trim whitespace
        cmd=$(echo "$cmd" | xargs)
        if declare -f "$cmd" > /dev/null; then
            activate-venv
            "$cmd"
        else
            echo "Unknown command: $cmd"
            help
            exit 1
        fi
    done
elif declare -f "$1" > /dev/null; then
    activate-venv || true
    "$1" "${@:2}"
else
    case "$1" in
        *)
            echo "Unknown command: $1"
            help
            exit 1
            ;;
    esac
fi
