#!/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

function help-table() {
    CMD_WIDTH=25
    OPT_WIDTH=20
    DESC_WIDTH=90
    COLUMN="| %-${CMD_WIDTH}s | %-${OPT_WIDTH}s | %-${DESC_WIDTH}s |\n"

    printf "$COLUMN" "Command" "Option" "Description"
    echo "|$(printf '%*s' $((CMD_WIDTH + 2)) '' | tr ' ' '-')|$(printf '%*s' $((OPT_WIDTH + 2)) '' | tr ' ' '-')|$(printf '%*s' $((DESC_WIDTH + 2)) '' | tr ' ' '-')|"
    printf "$COLUMN" "activate-venv" "" "Activate virtualenv."
    printf "$COLUMN" "add-git-submodule" "[url] [path]" "Add git submodule."
    printf "$COLUMN" "archive-docker-tags" "" "Archive Docker image tags on hub older than 1 year."
    printf "$COLUMN" "build" "[Dockerfile][--push]" "Build Odoo Docker image."
    printf "$COLUMN" "backup-env-files" "[path]" "Archive and copy env files to target location."
    printf "$COLUMN" "build-vuepress" "" "Create Vuepress build."
    printf "$COLUMN" "change-uuid" "[env]" "Change database uuid via xmlrpc."
    printf "$COLUMN" "checkout" "[version]" "Checkout Odoo version."
    printf "$COLUMN" "checkout-git-folder" "" "Checkout git commit."
    printf "$COLUMN" "checkout-latest-revision" "[version]" "Checkout the latest revision of the Odoo version."
    printf "$COLUMN" "checkout-revision" "[revision]" "Load Odoo revision env var and checkout git folders."
    printf "$COLUMN" "commit-revision" "" "Commit all changes and tag with current revision"
    printf "$COLUMN" "commit-with-llm" "" "Commit with llm generated commit message."
    printf "$COLUMN" "commit-git-folder" "[message][path]" "Commit all changes in path."
    printf "$COLUMN" "clean-git-folder" "" "Clean git folder."
    printf "$COLUMN" "clear-assets" "[db]" "Clear all assets of Odoo database."
    printf "$COLUMN" "clear-filestore" "[db]" "Clear local filestore folder. No param will clear all filestores."
    printf "$COLUMN" "clear-views" "[db]" "Clear all views of Odoo database."
    printf "$COLUMN" "clock-odoo" "[db]" "Count custom line of codes."
    printf "$COLUMN" "clone-git-folder" "[submodule][version]" "Clone git folder listed in the .gitmodules file."
    printf "$COLUMN" "login-docker" "[user][token]" "Setup Docker Hub login credentials."
    printf "$COLUMN" "login-podman" "[user][token]" "Login into registry with podman."
    printf "$COLUMN" "container-ps" "" "List container processes."
    printf "$COLUMN" "copy-env" "[env][env]" "Copy env file."
    printf "$COLUMN" "create-git-feature-branch" "[path]" "Create feature branch for Odoo module."
    printf "$COLUMN" "create-git-mig-branch" "[path]" "Create migration branch for Odoo module."
    printf "$COLUMN" "create-module" "[path]" "Create new Odoo module from template."
    printf "$COLUMN" "create-module-repo" "[path]" "Initialize Odoo module repo from template."
    printf "$COLUMN" "create-nextcloud-env" "[env]" "Create env file for Nextcloud instance."
    printf "$COLUMN" "create-odoo-env" "[env]" "Create env file for Odoo instance."
    printf "$COLUMN" "create-revision" "[revision]" "Create new Odoo revision."
    printf "$COLUMN" "create-snippet" "[id]" "Create snippet from template."
    printf "$COLUMN" "debug" "[name]" "Debugg application. Options: native."
    printf "$COLUMN" "dev-vuepress" "" "Start Vuepress development server."
    printf "$COLUMN" "disable-mailserver" "[env]" "Disable mail server settings via xmlrpc."
    printf "$COLUMN" "disable-snippet" "[env][path]" "Disable snippet definition."
    printf "$COLUMN" "download-git-folder" "[grep]" "Download git folder listed in the .gitmodules file."
    printf "$COLUMN" "drop-db" "[db]" "Drop target Odoo database."
    printf "$COLUMN" "edit-env" "[env]" "Open env file in default editor."
    printf "$COLUMN" "exec" "[name][cmd]" "Run command in container."
    printf "$COLUMN" "export-website-data" "[env]" "Export website data from Odoo database."
    printf "$COLUMN" "generate-admin-passwd" "[pass]" "Generate hash for Odoo master password."
    printf "$COLUMN" "generate-module-docs" "[path]" "Generate readme file for module."
    printf "$COLUMN" "generate-module-model" "[path][model]" "Generate model in module folder."
    printf "$COLUMN" "generate-module-inherit" "[path][model]" "Generate inherited model in module folder."
    printf "$COLUMN" "generate-module-views" "[path][model]" "Generate model views in module folder."
    printf "$COLUMN" "generate-module-security" "[path][model]" "Generate model access file."
    printf "$COLUMN" "generate-module-snippet" "[path][model][ref]" "Generate snippet for referefenced view."
    printf "$COLUMN" "generate-module-wizard" "[path][model]" "Generate wizard for a model in module folder."
    printf "$COLUMN" "generate-ssh-keys" "" "Generate ssh key pair."
    printf "$COLUMN" "generate-pg-ssl-keys" "" "Generate PostgreSQL SSL key material."
    printf "$COLUMN" "get-addons-path" "" "Output addons path."
    printf "$COLUMN" "status-git-folder" "[path]" "Show status for git folder in path."
    printf "$COLUMN" "get-modules" "[path][option]" "Get list of modules in path. Option is 'basename' or 'dirname'."
    printf "$COLUMN" "get-module-version" "[path]" "Get module version from manifest."
    printf "$COLUMN" "help" "[grep]" "Show help for commands."
    printf "$COLUMN" "import-csv" "[db][path]" "Import data from csv. Filename must match PostgreSQL table name."
    printf "$COLUMN" "import-website-data" "[env]" "Import website data to Odoo database."
    printf "$COLUMN" "info" "" "Show values of project env vars."
    printf "$COLUMN" "init-db" "[db]" "Initialize the Odoo database."
    printf "$COLUMN" "init-module" "[db][path,module]" "Initialize Odoo module."
    printf "$COLUMN" "init-venv" "" "Initialize python virtual env."
    printf "$COLUMN" "install" "" "Install Odoo requirements in source folder."
    printf "$COLUMN" "install-odoo-scripts" "" "Install Odoo scripts."
    printf "$COLUMN" "install-requirements" "[db][path]" "Install python packages from requirements.txt."
    printf "$COLUMN" "install-snippet" "[env][path]" "Install snippet definition."
    printf "$COLUMN" "install-vuepress" "" "Install Node build dependencies."
    printf "$COLUMN" "lint" "" "Run precommit for this project."
    printf "$COLUMN" "lint-module" "[path]" "Run pylint odoo for module."
    printf "$COLUMN" "lint-module-repo" "[path]" "Run pylint odoo for modules in repo folder."
    printf "$COLUMN" "lint-snippets" "" "Run checks for all snippets."
    printf "$COLUMN" "list-env" "" "List env files."
    printf "$COLUMN" "list-git-folder" "[grep]" "List path and url of git folders."
    printf "$COLUMN" "list-modules" "[path]" "Get modules in path as bash array."
    printf "$COLUMN" "list-revision" "" "List available Odoo revisions."
    printf "$COLUMN" "list-versions" "" "List available Odoo versions."
    printf "$COLUMN" "load-dotenv" "" "Restore content of .env from pass entry."
    printf "$COLUMN" "load-env" "[env]" "Load and export env file."
    printf "$COLUMN" "load-language" "[db][lang]" "Install language package in Odoo db."
    printf "$COLUMN" "load-version" "[version]" "Load git refs from  version folder."
    printf "$COLUMN" "load-ssh-key" "" "Load SSH private key from env var."
    printf "$COLUMN" "load-latest-revision" "[version]" "Load the latest revision of the Odoo version."
    printf "$COLUMN" "load-revision" "[revision]" "Load env var from specified revision."
    printf "$COLUMN" "logs" "[name]" "Tail container logs. Default is 'odoo'."
    printf "$COLUMN" "ls-git-folder" "[grep]" "List git folders path space separated."
    printf "$COLUMN" "odoocli" "[param]" "Execute odoocli cli."
    printf "$COLUMN" "patch-database" "[db][path]" "Apply sql file to database."
    printf "$COLUMN" "push" "[Dockerfile]" "Publish Odoo container image."
    printf "$COLUMN" "pull-git-folder" "" "Pull all git folders listed in the .gitmodules file."
    printf "$COLUMN" "push-git-folder" "" "Push all git folders in path."
    printf "$COLUMN" "psql" "[db]" "Start interactive psql shell."
    printf "$COLUMN" "pytest-module" "[db][name,path]" "Run module tests with pytest."
    printf "$COLUMN" "release-module" "[path]" "Create GitHub release for a module."
    printf "$COLUMN" "remove" "[name]" "Remove containers and volumes."
    printf "$COLUMN" "remove-env" "[env]" "Remove environment config."
    printf "$COLUMN" "remove-git-folder" "" "Delete all git folders."
    printf "$COLUMN" "remove-git-submodule" "[path]" "Remove a git submodule."
    printf "$COLUMN" "remove-module" "[db][name]" "Remove target Odoo module."
    printf "$COLUMN" "remove-snippet" "[env][path]" "Remove snippet definition."
    printf "$COLUMN" "remove-venv" "" "Remove virtualenv."
    printf "$COLUMN" "rename-env" "[env][env]" "Rename env file."
    printf "$COLUMN" "record-with-memray" "[name]" "Record application memory usage with memray. Options: native."
    printf "$COLUMN" "record-with-py-spy" "[pid]" "Record and create flamechart for a process."
    printf "$COLUMN" "reset-git-folder" "" "Abort rebase and reset submodules listed in the .gitmodules file."
    printf "$COLUMN" "reset-views" "[db][key]" "Execute hard reset on views matching keys."
    printf "$COLUMN" "restart" "[name]" "Restart container."
    printf "$COLUMN" "restore-env-files" "[path]" "Extract and copy env files from backup file."
    printf "$COLUMN" "run" "[name][cmd]" "Run container with command."
    printf "$COLUMN" "save-dotenv" "" "Store content of .env in pass entry."
    printf "$COLUMN" "save-version" "" "Save git folder refs to version folder."
    printf "$COLUMN" "serve-vuepress" "" "Serve Vuepress build."
    printf "$COLUMN" "set-addons-path" "" "Set Odoo addons path env variable."
    printf "$COLUMN" "set-admin" "[db]" "Sets the password for the first user in database."
    printf "$COLUMN" "setup-mail" "[db]" "Setup mail config in Odoo db."
    printf "$COLUMN" "shell" "[db][code]" "Start interactive odoo shell or run code."
    printf "$COLUMN" "show-env" "[env]" "Output content of the env file."
    printf "$COLUMN" "show-odoo-mcp-config" "" "Show the Odoo MCP server config."
    printf "$COLUMN" "show-revision" "[revision]" "Show references of Odoo revision."
    printf "$COLUMN" "source" "" "Source the Python virtual env."
    printf "$COLUMN" "stage-git-folder" "[path]" "Stage all files in git folders in path."
    printf "$COLUMN" "start" "[name][db]" "Start application. Options: none, admin, db, mailgate, mailpit, native, odoo."
    printf "$COLUMN" "stop" "[name]" "Stop containers."
    printf "$COLUMN" "switch-git-folder" "[version]" "Switch branch for all git folders listed in the .gitmodules file."
    printf "$COLUMN" "sync-git-folder" "" "Switch, stash and pull all git folders."
    printf "$COLUMN" "template-compose" "" "Template the Docker compose file."
    printf "$COLUMN" "template-odoo-rc" "" "Template the Odoo config file."
    printf "$COLUMN" "template-repo" "[path]" "Update the repo folder from template."
    printf "$COLUMN" "test-image" "[Dockerfile]" "Test internals of Docker image."
    printf "$COLUMN" "test-module" "[db][name,path]" "Test target Odoo module."
    printf "$COLUMN" "test-module-repo" "[db][path]" "Test target Odoo modules in repo folder."
    printf "$COLUMN" "test-project" "[clean]" "Run tests for this project."
    printf "$COLUMN" "test-xmlrpc" "[env]" "Test json rpc connection."
    printf "$COLUMN" "translate-module" "[path][lang][db]" "Generate translation for Odoo module."
    printf "$COLUMN" "update-docs" "" "Update all project docs."
    printf "$COLUMN" "update-help-docs" "" "Write help table to task.md file."
    printf "$COLUMN" "update-module" "[db][name,path]" "Update target Odoo module."
    printf "$COLUMN" "update-module-license" "[grep]" "Update LICENSE file for each matching module."
    printf "$COLUMN" "update-module-list" "[db]" "Update module list of Odoo database."
    printf "$COLUMN" "update-modules-docs" "" "Update modules docs file."
    printf "$COLUMN" "update-revision-docs" "" "Update revisions doc file."
    printf "$COLUMN" "update-snippet" "[env][path]" "Update snippet definition."
    printf "$COLUMN" "update-snippet-docs" "" "Update snippets doc file."
    printf "$COLUMN" "update-with-llm" "[glob][prompt]" "Feed module files with prompt to LLM and apply file changes."
    printf "$COLUMN" "upgrade-module" "[path]" "Upgrade code of module from target Odoo version."
    printf "$COLUMN" "upgrade-odoo" "[db][version][mode]" "Run the Odoo upgrade scripts. Default mode ist 'test'."
    printf "$COLUMN" "upload-module" "[env][path]" "Zip and upload Odoo module."
    printf "$COLUMN" "version" "" "Show version of required tools."
    printf "$COLUMN" "visualize-dependencies" "[path]" "Generate visualizations of module dependencies."
    printf "$COLUMN" "zip-module" "[path]" "Create zip file for module."
}

function 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

PASS_ENTRY=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]')
ODOO_RC="odoo.conf"
TASK_CONFIG_DIR="$HOME/.config/odoo-build"
PGSSLROOTCERT="tmp/postgres-odoo/ca.crt"
export PGSSLROOTCERT

# Dynamic env vars

ODOO_VERSION=$(echo "$ODOO_REVISION" | cut -d'.' -f1-2)
ODOO_DATABASE=${ODOO_DATABASE:="$ODOO_VERSION"}
ODOO_INIT_DEMO_DATA=${ODOO_INIT_DEMO_DATA:="True"}
ODOO_LANGUAGE=${ODOO_LANGUAGE:="de_CH"}
ODOO_PORT=${ODOO_PORT:="8069"}
POSTGRES_IMAGE=${POSTGRES_IMAGE:="postgres:14-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_TAG=${CONTAINER_TAG:="odoo:${ODOO_REVISION}"}
CONTAINER_CONFIG="$HOME/.docker/$CONTAINER_REGISTRY"
BROWSER_OPEN=${BROWSER_OPEN:=true}
CONTAINER_ENGINE=${CONTAINER_ENGINE:="docker"}
COMPOSE_COMMAND=${COMPOSE_COMMAND:="docker compose"}
LLM_MODEL=${LLM_MODEL:="llama"}
SMTP_SERVER=${SMTP_SERVER:="mailpit"}
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"}

# Conditional env vars

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

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

if [ "$ODOO_INIT_DEMO_DATA" = "False" ]; then
    ODOO_PARAM="--without-demo=all"
fi

# Check commands

if ! (command -v grep &> /dev/null && grep --version | grep -q GNU) && [[ "$OS_RELEASE" =~ ^(Darwin)$ ]]; then
    echo "Error: On Darwin systems, GNU grep must be installed."
    exit 1
fi

if ! command -v envsubst &> /dev/null; then
    echo "Error: 'envsubst' is not available. Install 'gettext' package."
    exit 1
fi

# Import commands

function 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

function info() {
    set-addons-path
    echo "OS Release: $OS_RELEASE"
    echo "Pass Entry: $PASS_ENTRY"
    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 "Container Registry: $CONTAINER_REGISTRY"
    echo "Container Config: $CONTAINER_CONFIG"
    echo "Container Tag: $CONTAINER_TAG"
    echo "Addons Path: $ADDONS_PATH"
}

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

function generate-admin-passwd() {
    activate-venv
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    ODOO_PASSWORD="$1"
    bin/hash_password
}

function show-odoo-mcp-config() {
    echo "Add this config to your IDE:  "
    cat << EOF
{
    "mcpServers": {
        "odoo": {
            "command": "$PWD/venv$ODOO_VERSION/bin/python",
            "args": ["$PWD/images/odoo-mcp/bin/server"],
            "env": {
                "ODOO_URL": "http://localhost:8069",
                "ODOO_DATABASE": "$ODOO_DATABASE",
                "ODOO_USERNAME": "admin",
                "ODOO_PASSWORD": "admin"
            }
        }
    }
}
EOF
}

# Env Commands

function 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
}

function activate-venv() {
    echo "Source virtualenv venv$ODOO_VERSION."
    source "venv$ODOO_VERSION/bin/activate"
    echo "$(python --version) is active."
}

function remove-venv() {
    echo "Remove virtualenv venv$ODOO_VERSION."
    rm -rf "venv$ODOO_VERSION"
}

function get-addons-path() {
    set-addons-path
    echo "$ADDONS_PATH"
}

function 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

    # Deode SSH private key
    if [ -n "$GIT_SSH_PRIVATE_KEY" ]; then
        GIT_SSH_PRIVATE_KEY=$(echo -e "$GIT_SSH_PRIVATE_KEY" | base64 -w0)
    fi

    export BASE_URL_WEBSITE_PARAM=$([ -n "$BASE_URL_WEBSITE" ] && echo "base_url_website = $BASE_URL_WEBSITE")
    export BASE_URL_APP_PARAM=$([ -n "$BASE_URL_APP" ] && echo "base_url_app = $BASE_URL_APP")
    export GIT_SSH_PUBLIC_KEY_PARAM=$([ -n "$GIT_SSH_PUBLIC_KEY" ] && echo "git.ssh.public.key = \"$GIT_SSH_PUBLIC_KEY\"")
    export GIT_SSH_PRIVATE_KEY_PARAM=$([ -n "$GIT_SSH_PRIVATE_KEY" ] && echo "git.ssh.private.key = \"$GIT_SSH_PRIVATE_KEY\"")
    export UPLOADCARE_PUBLIC_KEY_PARAM=$([ -n "$UPLOADCARE_PUBLIC_KEY" ] && echo "uploadcare.public_key = $UPLOADCARE_PUBLIC_KEY")
    export UPLOADCARE_SECRET_KEY_PARAM=$([ -n "$UPLOADCARE_SECRET_KEY" ] && echo "uploadcare.secret_key = $UPLOADCARE_SECRET_KEY")
    export KEYCLOAK_BASE_URL_PARAM=$([ -n "$KEYCLOAK_BASE_URL" ] && echo "keycloak.base_url = $KEYCLOAK_BASE_URL")
    export KEYCLOAK_CLIENT_ID_PARAM=$([ -n "$KEYCLOAK_CLIENT_ID" ] && echo "keycloak.client_id = $KEYCLOAK_CLIENT_ID")
    export KEYCLOAK_CLIENT_SECRET_PARAM=$([ -n "$KEYCLOAK_CLIENT_SECRET" ] && echo "keycloak.client_secret = $KEYCLOAK_CLIENT_SECRET")
    export KEYCLOAK_REALM_PARAM=$([ -n "$KEYCLOAK_REALM" ] && echo "keycloak.realm = $KEYCLOAK_REALM")

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

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

    # Remove excluded addons
    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")
    done

    # Check if addons dir exist
    for ADDON in $GIT_FOLDERS_PATH; do
        if [ -d "$ADDON" ]; then
            echo "Path does not exist: $ADDON"
            exit 1
        fi
    done

    # Convert to native paths
    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
}

function load-ssh-key() {
    if [ -n "$GIT_SSH_PRIVATE_KEY" ]; then
        echo "Setup SSH key from env var."
        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
}

function list-versions() {
    ls -1 versions
}

# Docs Commands

function lint() {
    activate-venv

    echo "Run pre-commit in $PWD"
    pre-commit run
}

function update-help-docs() {
    echo "Pipe help table into task.md."
    help-table > task.md
}

function update-snippet-docs() {
    ./bin/update-snippet-docs
}

function update-revision-docs() {

    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
    PATH_URL="https://github.com/Mint-System/Odoo-Build/tree/main"

    for FILE in $(ls revisions/ | sort -rV); do
        REVISION="$FILE"
        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
}

function update-modules-docs() {
    echo "Update module doc file."
    activate-venv
    ./bin/generate-module-overview
}

function update-docs() {
    update-modules-docs
    update-revision-docs
    update-snippet-docs
    update-help-docs
}

function update-module-license() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    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
}

function template-repo(){
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    REPO_PATH="$1"

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

    echo "Template README.md to $REPO_PATH."
    REPO_NAME=$(basename "$REPO_PATH")
    REPO_MODEL=${REPO_NAME%%_*}
    REPO_TITLE=$(echo "$REPO_NAME" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g')
    export REPO_MODEL REPO_TITLE REPO_NAME
    envsubst '$REPO_MODEL $REPO_TITLE $REPO_NAME' < 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 "$REPO_PATH/.flake8"
    rm "$REPO_PATH/.isort.cfg"
    rm "$REPO_PATH/.pylintrc-mandatory"
}

# Module Commands

function get-modules() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

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

    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]+['\"]|${ODOO_VERSION})" |  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]+['\"]|${ODOO_VERSION})" | xargs -r dirname | xargs -r -I {} basename {} | sort -u | tr "\n" "," | sed 's/,$//')
    fi
    echo "$MODULES"
}

function get-module-version() {
    if test -z "$1"
    then
        echo "\$1 is empty."
    else
        # Get version of module
        VERSION=$(grep -m 1 \"version "$1/__manifest__.py" |  sed "s;';\";g"  | sed "s/,//g" | sed  's/#.*//g')
        VERSION=$(echo "{ $VERSION }" | jq .version | sed 's/"//g' | sed 's/null//g')

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

        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
}

function list-modules() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    MODULES=$(get-modules "$1" dirname)
    echo "$MODULES" | tr ',' '\n'
}

# Container Commands

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

function 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

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

function container-ps() {
    $CONTAINER_ENGINE ps
}

function build() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    MANIFEST_FILE="$1"

    # Set container tag and platform based on Dockerfile
    PLATFORM="linux/amd64"
    if [ "$MANIFEST_FILE" = "images/odoo/Dockerfile" ]; then
        PLATFORM="linux/amd64,linux/arm64"
    fi
    if [ "$MANIFEST_FILE" = "images/odoo-mailgate/Dockerfile" ]; then
        CONTAINER_TAG="odoo-mailgate"
    elif [ "$MANIFEST_FILE" = "images/odoo-upgrade/Dockerfile" ]; then
        CONTAINER_TAG="odoo-upgrade"
    elif [ "$MANIFEST_FILE" = "images/odoocli/Dockerfile" ]; then
        CONTAINER_TAG="odoocli"
    elif [ "$MANIFEST_FILE" = "images/odoo-mcp/Dockerfile" ]; then
        CONTAINER_TAG="odoo-mcp"
    fi

    # Load Odoo revision
    ODOO_DATE=$(echo "$ODOO_REVISION" | cut -d'.' -f3-)
    source "revisions/$ODOO_REVISION"

    # Select Python version
    case "$ODOO_VERSION" in
        "15.0")
            PYTHON_VERSION="3.9"
            ;;
        "16.0")
            PYTHON_VERSION="3.11"
            ;;
        "17.0")
            PYTHON_VERSION="3.11"
            ;;
        "18.0")
            PYTHON_VERSION="3.12"
            ;;
        *)
            PYTHON_VERSION="3.12"
            ;;
    esac

    # Set platform
    DOCKER_OUTPUT="--load"
    if [ "$2" = "--push" ]; then
        DOCKER_OUTPUT="--push"
    fi

    # Output configuration
    echo "Python Version: $PYTHON_VERSION"
    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" \
        --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}" \
        "$DOCKER_OUTPUT"
}

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

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

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

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

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

    echo "Docker login with username $CONTAINER_REGISTRY_USERNAME."
    echo "$CONTAINER_REGISTRY_PASSWORD" | docker --config "$CONTAINER_CONFIG" login --username "$CONTAINER_REGISTRY_USERNAME" --password-stdin
}

function 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
}

function test-image() {
    if [ "$1" == "images/odoo-upgrade/Dockerfile" ]; then
        CONTAINER_TAG="odoo-upgrade"
    fi
    if [ "$1" == "images/odoocli/Dockerfile" ]; then
        CONTAINER_TAG="odoocli"
    fi

    if [[ "$CONTAINER_TAG" =~ ^odoo: ]]; then
        echo -e "\033[38;5;214mTEST\033[0m: Run git clone addons"

        generate-ssh-keys
        export GIT_SSH_PRIVATE_KEY
        export GIT_SSH_PUBLIC_KEY
        export LOCAL_PATH="./tmp"
        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}"
        mkdir -p "$LOCAL_PATH"
        export PATH=./images/odoo/bin/:$PATH
        ./images/odoo/bin/clone-git-addons
        rm -rf "$LOCAL_PATH/github.com"

        echo -e "\033[38;5;214mTEST\033[0m: Check addons path regex with version: $ODOO_VERSION"
        echo "'version': '1.0'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})"
        echo "'version': '1.1'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})"
        echo "'version': '2.5'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})"
        echo "'version': '13.0.1.0.0'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" || echo "Must not match"
        echo "'version': '14.0.2.1.1'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})" || echo "Must not match"
        echo "'version': '${ODOO_VERSION}.0.0.0'" | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})"
        echo '"Version": "1.0"' | grep -iE "version.*['\"]([0-9]+\.[0-9]+['\"]|${ODOO_VERSION})"

        echo -e "\033[38;5;214mTEST\033[0m: Install python package."
        export PYTHON_INSTALL="cowsay"
        activate-venv
        ./images/odoo/bin/install-python-packages
    fi

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

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

function push() {
    LATEST_TAG="odoo:$ODOO_VERSION"

    if [ "$1" == "images/odoo-mailgate/Dockerfile" ]; then
        CONTAINER_TAG="odoo-mailgate"
        LATEST_TAG="odoo-mailgate:latest"
    elif [ "$1" == "images/odoo-upgrade/Dockerfile" ]; then
        CONTAINER_TAG="odoo-upgrade"
        LATEST_TAG="odoo-upgrade:latest"
    elif [ "$1" == "images/odoocli/Dockerfile" ]; then
        CONTAINER_TAG="odoocli"
        LATEST_TAG="odoocli:latest"
    elif [ "$1" == "images/odoo-mcp/Dockerfile" ]; then
        CONTAINER_TAG="odoo-mcp"
        LATEST_TAG="odoocli:latest"
    fi

    echo "Tag container image ${CONTAINER_REGISTRY}/${CONTAINER_TAG} with ${CONTAINER_REGISTRY}/${LATEST_TAG}"
    $CONTAINER_ENGINE tag "${CONTAINER_REGISTRY}/${CONTAINER_TAG}" "${CONTAINER_REGISTRY}/${LATEST_TAG}"

    if [ "$CONTAINER_ENGINE" == "docker" ]; then
        echo "Push container image to ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        docker --config "$CONTAINER_CONFIG" push "${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        docker --config "$CONTAINER_CONFIG" push "${CONTAINER_REGISTRY}/${LATEST_TAG}"
    fi

    if [ "$CONTAINER_ENGINE" == "podman" ]; then
        echo "Push container image to ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        podman push "${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        podman push "${CONTAINER_REGISTRY}/${LATEST_TAG}"
    fi
}

function upload-module() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    # Check if path is root folder
    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
            FILE_PATH="tmp/$MODULE_NAME.zip"
            load-env "$1"
            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\n"
            UPLOADS+="    version: $VERSION\n"
        fi
    done

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

function logs() {
    CONTAINER="$1"
    CONTAINER=${CONTAINER:="odoo"}
    $CONTAINER_ENGINE logs --follow "$CONTAINER"
}

# Script Commands

function install-odoo-scripts() {
    echo "Installing Odoo scripts with user flag."
    curl -L https://ansible.build/odoo_scripts/install | bash -s -- --user
}

function upgrade-odoo() {
    if test -z "$1"; then echo "\$1/database is empty."; exit; fi
    if test -z "$2"; then echo "\$2/version is empty."; exit; fi

    DATABASE="$1"
    ODOO_TARGET_VERSION="$2"
    MODE="test"
    if [ -n "$3" ]; then
        # Options: test, production
        MODE="$3"
    fi
    NEW_DATABASE="${DATABASE}_${ODOO_TARGET_VERSION}"

    echo "Execute Odoo upgrade script in $MODE mode for $DATABASE with version $ODOO_TARGET_VERSION."
    echo "The database will be restored as $NEW_DATABASE."
    export PGHOST=localhost
    export PGUSER=odoo
    export PGPASSWORD=odoo
    python <(curl -s https://upgrade.odoo.com/upgrade) "$MODE" -d "$DATABASE" -t "$ODOO_TARGET_VERSION" -r "$NEW_DATABASE"
    echo "Upgrade finished successfully."
}

# Install Commands

function install() {
    init-venv
    activate-venv

    if [[ "$OS_RELEASE" =~ ^(Debian|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;214mPython\033[0m: Install psycopg2-binary"
        uv pip install psycopg2-binary

        echo -e "\033[38;5;214mPython\033[0m: Remove psycopg2 from Odoo requirements"
        sed -i '/^psycopg2/d' odoo/requirements.txt

    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 brew "
        brew install gettext libsasl2 openldap openssl libxml2 grep

        echo -e "\033[38;5;214mPython\033[0m: Install the binary version of psycopg2"
        uv pip install psycopg2-binary

        echo -e "\033[38;5;214mPython\033[0m: Remove psycopg2 from Odoo requirements"
        sed -i '/^psycopg2/d' odoo/requirements.txt

    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

        echo -e "\033[38;5;214mPython\033[0m: Install the binary version of psycopg2"
        uv pip install psycopg2-binary

        echo -e "\033[38;5;214mPython\033[0m: Remove psycopg2 from Odoo requirements"
        sed -i '/^psycopg2/d' odoo/requirements.txt

        echo -e "\033[38;5;214mPython\033[0m: Remove python-ldap from Odoo requirements"
        sed -i '/^python-ldap/d' odoo/requirements.txt
    else
        echo "The operating system $OS_RELEASE is not supported."
        exit 1
    fi

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

        # Source: https://gist.github.com/faniska/37f896d5e9de5fee925925d7caf3cb9e
        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

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

    echo -e "\033[38;5;214mPython\033[0m: Install Odoo development tools"
    if [ -n "$PYTHON_NOTIFY" ]; then
        uv pip install "$PYTHON_NOTIFY"
    fi

    uv pip install -r requirements.txt
    if [ ! -d "upgrade-util" ]; then
        git clone https://github.com/odoo/upgrade-util
    fi
    # uv pip install git+https://github.com/odoo/upgrade-util@master
    if [ -f "odoo/requirements.txt" ]; then

        echo -e "\033[38;5;214mPython\033[0m: Install Odoo dependencies"
        uv pip install -r odoo/requirements.txt

        if [ "$ODOO_VERSION" = "17.0" ]; then
            uv pip install lxml_html_clean Werkzeug==2.2.2
        fi

        echo -e "\033[38;5;214mPython\033[0m: Configure Odoo as python package"
        uv pip install -e odoo --no-deps
    fi

    if [[ "$OS_RELEASE" =~ ^(Fedora Linux)$ ]]; then
        echo -e "\033[38;5;214mGit\033[0m: Reset Odoo requirements"
        git -C odoo checkout requirements.txt
    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
}

# Process Commands

function open-url-with-delay(){
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if $BROWSER_OPEN; then
        sleep 2
        $OS_OPEN "$1"
    fi
}

function start() {
    set-addons-path
    if [ "$POSTGRES_SSL" = "on" ]; then
        generate-pg-ssl-keys
    fi
    template-compose

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

    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" =~ "admin" ]]; then
        echo "Open http://localhost:8000 url in your browser."
        open-url-with-delay "http://localhost:8000" & $COMPOSE_COMMAND up -d admin
    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-with-delay "http://localhost:8025" & $COMPOSE_COMMAND up -d mailpit
    fi

    if [[ "$1" =~ "native" ]]; then
        activate-venv
        export SMTP_SERVER=localhost
        template-odoo-rc
        PORT=8069
        while [ $PORT -le 8079 ]; do
            if ! lsof -i:$PORT >/dev/null 2>&1; then
                break
            fi
            PORT=$((PORT + 1))
        done
        export ODOO_BASE_URL="http://localhost:8069"

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

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

function debug() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    activate-venv
    set-addons-path

    PORT=8069

    if [[ "$1" =~ "native" ]]; 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" \
            --xmlrpc-port="$PORT" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
    fi
}

function run() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    activate-venv
    template-compose

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

function exec() {
    if test -z "$1"; then echo "\$1 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
}

function record-with-py-spy() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    PID="$1"
    OUTPUT_FILE="tmp/speedscope-capture.json"
    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 --

    $OS_OPEN "$SPEEDSCOPE_URL"
}

function record-with-memray() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    activate-venv
    set-addons-path

    PORT=8069
    OUTPUT_FILE="tmp/memray-capture.bin"

    if [[ "$1" =~ "native" ]]; 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" \
            --xmlrpc-port="$PORT" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
        python3 -m memray flamegraph "$OUTPUT_FILE"
        $OS_OPEN "tmp/memray-flamegraph-output.html"
    fi
}

function remove() {
    template-compose

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

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

    if [[ "$1" =~ "admin" ]]; then
        $COMPOSE_COMMAND rm -f -s -v admin
    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
}

function restart() {
    template-compose

    if test -z "$1"; then
        $COMPOSE_COMMAND restart
    fi

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

function stop() {
    template-compose

    if test -z "$1"; then
        $COMPOSE_COMMAND stop
    fi

    $COMPOSE_COMMAND stop "$1"
}

function 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
}

function generate-pg-ssl-keys() {
    KEYMATERIAL_PATH="tmp/postgres-odoo"
    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

            chmod 600 "$KEYMATERIAL_PATH/server.key"
            chmod 644 "$KEYMATERIAL_PATH/server.crt"
            sudo chown 70:70 "$KEYMATERIAL_PATH/server.key"
            sudo chown 70:70 "$KEYMATERIAL_PATH/server.crt"
        fi
    fi
}

function test-project() {
    if [ "$1" = "clean" ]; then
        echo -e "\033[38;5;214mTEST\033[0m: Cleanup test environment."
        drop-db test
        remove odoo
        git -C oca/partner-contact clean -df
        git -C oca/partner-contact checkout .
        remove-env test
    else
        activate-venv
        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},https://github.com/Mint-System/Odoo-Apps-Connector.git#${ODOO_VERSION}"
        run odoo git-clone-addons

        echo -e "\033[38;5;214mTEST\033[0m: Init Odoo database."
        export ODOO_ADDONS_PATH="/mnt/oca/partner-contact"
        export ODOO_DATABASE=test
        export ODOO_INIT_DEMO_DATA=True
        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,git_base,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 SESSION_DB_URI=postgres://odoo:odoo@db/test
        export MODULE_AUTO_INSTALL_DISABLED=odoo_test_xmlrunner
        export AUTO_UPDATE_MODULES=True
        export AUTO_UPDATE_TRANSLATIONS=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: Init OCA module."
        run 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_USERNAME=test
        export ODOO_PASSWORD=test
        if ! show-env test; then
            create-odoo-env test
        fi
        show-env test
        test-xmlrpc test

        echo -e "\033[38;5;214mTEST\033[0m: Install, update, disable and remove snippet."
        install-snippet test snippets/base.view_partner_form.show_type.xml
        update-snippet test snippets/base.view_partner_form.show_type.xml
        disable-snippet test snippets/base.view_partner_form.show_type.xml
        remove-snippet test snippets/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
        restart odoo
        until curl --silent --fail "http://localhost:8069/web/login" &> /dev/null; do
            echo "Waiting for Odoo to be ready..."
            sleep 3
        done
        docker logs odoo
        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

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

        echo -e "\033[38;5;214mTEST\033[0m: Prepare container for testing."
        exec odoo setup-tests

        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 upgrade."
        mkdir -p oca/partner-contact/partner_firstname/migrations/${ODOO_VERSION}.2.0.0
        cat << EOF > oca/partner-contact/partner_firstname/migrations/${ODOO_VERSION}.2.0.0/post-migration.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 += "!"

    _logger.info("Updated %s partners", len(partners))
EOF
        sed -i "/\"version\":/c\    \"version\": \"${ODOO_VERSION}.2.0.0\"," oca/partner-contact/partner_firstname/__manifest__.py
        exec odoo update-module partner_firstname 2>&1 | grep "module partner_firstname: Running migration"
    fi
}

# Interactive Shell Commands

function shell() {
    DATABASE="$1"
    if [ -z "$DATABASE" ]; then
        DATABASE="$ODOO_DATABASE"
    fi
    CODE="$2"

    activate-venv
    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
}

function psql() {
    DATABASE="$1"
    if [ -z "$DATABASE" ]; then
        DATABASE="$ODOO_DATABASE"
    fi

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

# Database Commands

function init-db() {
    ODOO_INIT_ADDONS="${ODOO_INIT_ADDONS:-"web"}"
    DATABASE="$1"
    if [ -z "$DATABASE" ]; then
        DATABASE="$ODOO_DATABASE"
    fi

    activate-venv
    set-addons-path
    template-odoo-rc

    echo "Initialize database $DATABASE with $ODOO_INIT_ADDONS."
    if [[ -n "$ODOO_PARAM" ]]; then
        odoo -d "$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 -d "$DATABASE" -i "$ODOO_INIT_ADDONS" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE"
    fi
}

function import-csv() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    DATABASE="$1"
    FILE_PATH="$2"

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        FILE_PATH="$1"
    fi

    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;"
}

function drop-db() {
    DATABASE="$1"
    if [ -z "$DATABASE" ]; then
        DATABASE="$ODOO_DATABASE"
    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\";"
}

function setup-mail() {
    DATABASE="$1"
    if [ -z "$DATABASE" ]; then
        DATABASE="$ODOO_DATABASE"
    fi

    activate-venv
    set-addons-path

    export MAIL_BOUNCE_ALIAS
    export MAIL_CATCHALL_ALIAS
    export MAIL_CATCHALL_DOMAIN
    export MAIL_DEFAULT_FROM
    export MAIL_ALIAS_DOMAIN

    echo "Setup mail server configuation for database $DATABASE."
    odoo shell --database "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http < bin/setup-mail
}

function load-language() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    DATABASE="$1"
    LANGUAGE="$2"

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        LANGUAGE="$1"
    fi

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

function set-admin() {
    DATABASE="$1"
    if [ -z "$DATABASE" ]; then
        DATABASE="$ODOO_DATABASE"
    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;"
}

function clear-assets() {
    DATABASE="$1"
    if [ -z "$DATABASE" ]; then
        DATABASE="$ODOO_DATABASE"
    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_%';"
}

function clear-views() {
    DATABASE="$1"
    if [ -z "$DATABASE" ]; then
        DATABASE="$ODOO_DATABASE"
    fi

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

    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);"
}

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

function disable-mailserver() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    activate-venv
    load-env "$1"
    images/odoocli/bin/odoocli --method 'write' --model 'ir.mail_server' --field 'active' --value False --domain "[]"
    images/odoocli/bin/odoocli --method 'write' --model 'fetchmail.server' --field 'active' --value False --domain "[]"
}

function change-uuid() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    activate-venv
    load-env "$1"
    UUID=$(uuidgen)
    echo "Set uuid $UUID for $1."
    images/odoocli/bin/odoocli --method 'write' --model 'ir.config_parameter' --domain "[('key', '=', 'database.uuid')]" --field 'value' --value "'$UUID'"
}

function update-module-list() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

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

function patch-database() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    DATABASE="$1"
    FILENAME="$2"

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        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

function clock-odoo() {
    DATABASE="$1"
    if [ -z "$DATABASE" ]; then
        DATABASE="$ODOO_DATABASE"
    fi

    activate-venv
    set-addons-path
    odoo cloc -d "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http
}

function visualize-dependencies() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    mkdir -p tmp
    ./bin/odoo-module-dependencies "$1"
    $OS_OPEN tmp/odoo-module-dependencies.html
}

# Repo Commands

function install-requirements() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    DATABASE="$1"
    FILE_PATH="$2"

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        FILE_PATH="$1"
    fi

    FILE_NAME=$(basename "$FILE_PATH")
    if [[ "$FILE_NAME" != "requirements.txt" ]]; then echo "\$1 must point to requirements.txt file."; exit; fi

    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
        activate-venv
        echo "Installing python packages in virutal env..."
        uv pip install -r "$FILE_PATH"
    fi
}

function create-module-repo() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    REPO_PATH="$1"
    REPO_NAME=$(basename "$REPO_PATH")

    # If repo already exists, create new branch
    if [ -d "$REPO_PATH/.git" ]; then
        echo "Create new branch $REPO_NAME in $REPO_PATH"
        cd "$REPO_PATH" || exit
        git switch --orphan -c "$ODOO_VERSION"

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

    echo "Copy template files from templates/$ODOO_VERSION to $REPO_PATH"
    cp "templates/$ODOO_VERSION/*" "$REPO_PATH"
}

function lint-module-repo() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    MODULE_PATHS=$(get-modules "$MODULE_PATH")

    for MODULE_PATH in ${MODULE_PATHS//,/ }; do
        lint-module "$MODULE_PATH"
    done
}

function test-module-repo() {
    DATABASE="$1"
    MODULE_PATH="$2"

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        MODULE_PATH="$1"
    fi

    MODULES=$(get-modules "$MODULE_PATH" basename)

    for MODULE in ${MODULES//,/ }; do
        echo "Testing module ${MODULE}..."
        test-module "$DATABASE" "$MODULE"
    done
}

# Module Commands

function init-module() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    DATABASE="$1"
    MODULE_PATH="$2"

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        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
        MODULES=$(get-modules "$MODULE_PATH" basename)
    else
        MODULES=$(basename "$MODULE_PATH")
    fi

    activate-venv
    set-addons-path
    echo "Initialize module $MODULES on ${DATABASE}..."
    odoo -d "$DATABASE" -i "$MODULES" --config "$ODOO_RC" \
        --addons-path="$ADDONS_PATH" --stop-after-init --no-http
}

function upgrade-module() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    MODULE="$1"

    activate-venv
    set-addons-path

    if [ "$ODOO_VERSION" = "17.0" ]; then
        echo "Migrate views of module $MODULE to ${ODOO_VERSION}..."
        odoo --database "$DATABASE" -i "$MODULE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --load=base,web,views_migration_17 --stop-after-init
    fi

    SOURCE_ODOO_VERSION=$(get-module-version $MODULE | cut -d'.' -f1,2)

    if [ "$ODOO_VERSION" = "18.0" ]; then
        echo "Upgrade code of module $MODULE from ${SOURCE_ODOO_VERSION}..."
        odoo upgrade_code --addons-path "$MODULE" --from "$SOURCE_ODOO_VERSION"
    fi

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

function update-module() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    DATABASE="$1"
    MODULE_NAME=$(basename "$2")

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        MODULE_NAME=$(basename "$1")
    fi

    activate-venv
    set-addons-path
    echo "Updating module $MODULE_NAME on ${DATABASE}..."
    if [[ -n "$ODOO_PARAM" ]] ; then
        odoo --upgrade-path=upgrade-util/src --database "$DATABASE" --update "$MODULE_NAME" \
            --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http "$ODOO_PARAM"
    else
        odoo --upgrade-path=upgrade-util/src --database "$DATABASE" --update "$MODULE_NAME" \
            --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http
    fi
}

function translate-module() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    MODULE_PATH="$1"
    MODULE_NAME=$(basename "$1")
    LANGUAGE="$2"
    DATABASE="$3"

    if test -z "$2"; then
        LANGUAGE="$ODOO_LANGUAGE"
    fi

    if test -z "$3"; then
        DATABASE="$ODOO_DATABASE"
    fi

    activate-venv
    set-addons-path
    echo "Generate $LANGUAGE tranlsation file for $MODULE_NAME on ${DATABASE}..."
    mkdir -p "$MODULE_PATH/i18n/"
    odoo -d "$DATABASE" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --modules "$MODULE_NAME" -l "$LANGUAGE" --i18n-export "$MODULE_PATH/i18n/$LANGUAGE.po"

    # 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
}

function lint-module() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    activate-venv

    echo "Set file and folder permissions on $1"
    find "$1" -type d -exec chmod u=rwx,go=rx {} \;
    find "$1" -type f -exec chmod u=rw,go=r {} \;

    echo "Update index.html"
    rst2html5 "$1/README.rst"  "$1/static/description/index.html"

    pushd .
    cd "$1"

    echo "Run pre-commit in $PWD"
    pre-commit run --all-files # --show-diff-on-failure --color=always

    echo "Stage changes"
    git  add .

    popd
}

function test-module() {
    DATABASE="$1"
    MODULE_NAME=$(basename "$2")

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        MODULE_NAME=$(basename "$1")
    fi

    activate-venv
    set-addons-path
    echo "Testing module $MODULE_NAME on ${DATABASE}..."
    odoo -d "$DATABASE" -i "$MODULE_NAME" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-tags /"$MODULE_NAME"
}

function pytest-module() {
    DATABASE="$1"
    MODULE_PATH="$2"

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        MODULE_PATH="$1"
    fi

    activate-venv
    set-addons-path

    ODOO_CONFIG="tmp/odoo.conf"
    echo "Write $ODOO_CONFIG"

    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"

    pytest -s --odoo-http --odoo-config="$ODOO_CONFIG" --odoo-database="$DATABASE" "$MODULE_PATH"
}

function remove-module() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    DATABASE="$1"
    MODULE_NAME=$(basename "$2")

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        MODULE_NAME=$(basename "$1")
    fi

    activate-venv
    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
}

function create-module() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    MODULE_NAME=$(basename "$1")
    PARENT_DIR=$(dirname "$1")

    activate-venv
    echo "Scaffolding module $MODULE_NAME in ${PARENT_DIR}..."
    odoo scaffold "$MODULE_NAME" "$PARENT_DIR" -t templates/module
}

function generate-module-docs() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    MODULE_PATH="$1"
    MANIFEST_PATH="$MODULE_PATH/__manifest__.py"

    MODULE_TITLE=$(python3 -c "import ast; d=ast.literal_eval(open('$MANIFEST_PATH').read()); print(d.get('name', ''))")
    MODULE_UNDERLINE=$(python3 -c "import ast; d=ast.literal_eval(open('$MANIFEST_PATH').read()); print('=' * len(d.get('name', '')))")
    MODULE_SUMMARY=$(python3 -c "import ast; d=ast.literal_eval(open('$MANIFEST_PATH').read()); print(d.get('summary', '').replace('\n', ' ').strip())")
    MODULE_WIKI_PATH=$(basename "$MODULE_PATH" | sed 's/_/-/g')

    echo "Update $MODULE_PATH/README.rst"
    export MODULE_TITLE MODULE_UNDERLINE MODULE_SUMMARY MODULE_WIKI_PATH
    envsubst '$MODULE_TITLE $MODULE_UNDERLINE $MODULE_SUMMARY $MODULE_WIKI_PATH' < templates/README.rst > "$MODULE_PATH/README.rst"

    echo "Copy app icon"
    cp templates/module/static/description/icon.png "$MODULE_PATH/static/description/icon.png"

    echo "Copy license"
    cp templates/module/LICENSE "$MODULE_PATH/"
}

function generate-module-security() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    MODULE_NAME=$(basename "$1")
    PARENT_DIR=$(dirname "$1")
    MODEL_DOT_NAME="$2"
    MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_')

    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"
}

function generate-module-model() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    MODULE_PATH="$1"
    MODULE_NAME=$(basename "$1")

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

    echo "Substitute model template"
    envsubst '$NAME $MODULE_NAME $MODEL_DOT_NAME $MODEL_NAME $MODEL_DESCRIPTION $MODEL_CAMEL_NAME' < 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.
}

function generate-module-inherit() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    MODULE_PATH="$1"
    MODULE_NAME=$(basename "$1")

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

    echo "Substitute model template"
    envsubst '$NAME $MODULE_NAME $MODEL_DOT_NAME $MODEL_NAME $MODEL_DESCRIPTION $MODEL_CAMEL_NAME' < 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.
}

function generate-module-views() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    MODULE_PATH="$1"
    MODULE_NAME=$(basename "$1")

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

    echo "Substitute view template"
    mkdir -p "$MODULE_PATH/views"
    envsubst '$NAME $MODULE_NAME $MODEL_DOT_NAME $MODEL_NAME $MODEL_DESCRIPTION $MODEL_CAMEL_NAME' < templates/model_views.xml > "$MODULE_PATH/views/${MODEL_NAME}_views.xml"
    echo "Update file $MODULE_NAME/__manifest__.py with:"
    echo -e "\t\"views/${MODEL_NAME}_views.xml\","
}

function generate-module-snippet() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    MODULE_PATH="$1" # addons/sale_workflow/sale_helm
    MODEL_DOT_NAME="$2" # sale.order
    VIEW_REF="$3" # sale.view_order_form

    # Generate template values
    MODEL_NAME=${MODEL_DOT_NAME//\./_}
    MODULE_NAME=$(basename "$1")
    SRC_MODULE_NAME=$(echo "$VIEW_REF" | cut -d"." -f1)
    XML_ID=$(echo "$VIEW_REF" | cut -d"." -f2)
    export MODULE_NAME MODEL_DOT_NAME SRC_MODULE_NAME VIEW_REF XML_ID

    echo "Substitute snippet template"
    envsubst '$MODULE_NAME $MODEL_DOT_NAME $SRC_MODULE_NAME $VIEW_REF $XML_ID' < templates/model_snippet.xml > "$MODULE_PATH/views/${MODEL_NAME}_views.xml"
    echo "Update file $MODULE_NAME/__manifest__.py with:"
    echo -e "\t, \"views/${MODEL_NAME}_views.xml\""
}

function release-module() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    VERSION=$(get-module-version "$1")
    cd "$1" || exit
    LAST_TAG=$(git describe --tags)
    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"
}

function zip-module() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

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

    # Copy module to tmp folder
    echo "Copy module $MODULE version $VERSION to tmp folder"
    MODULE_NAME="$MODULE-$VERSION"
    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
}

# Snippet Commands

function reset-views() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    DATABASE="$1"
    KEY=$(basename "$2")

    # Use default database if second param is not given
    if test -z "$2"; then
        DATABASE="$ODOO_DATABASE"
        KEY=$(basename "$1")
    fi

    activate-venv
    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
}

function create-snippet() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

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

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

function install-snippet() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    # Generate view name from filename
    FILE=$(basename "$2")
    NAME="mint_system.${FILE%.xml}"
    INHERIT_ID=$(echo 'cat //data/@inherit_id' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
    TYPE=$(echo 'cat //data/@type' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
    MODEL=$(echo 'cat //data/@model' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
    PRIORITY=$(echo 'cat //data/@priority' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
    MODULE_NAME=$(echo "$INHERIT_ID" | cut -d. -f 1 )
    XML_ID=$(echo "$INHERIT_ID" | cut -d. -f 2 )

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

    activate-venv
    load-env "$1"
    echo "Search for view with name $MODULE_NAME.$XML_ID"
    INHERIT_ID=$(images/odoocli/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/odoocli/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")'''
    }"
}

function update-snippet() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    # Generate view name from filename
    FILE=$(basename "$2")
    NAME="mint_system.${FILE%.xml}"

    activate-venv
    load-env "$1"
    echo "Update view with name $NAME"
    images/odoocli/bin/odoocli --method 'write' --model 'ir.ui.view' --domain "[('name', '=', '$NAME')]" --field 'arch_base' --value "'''$(cat "$2")'''"
}

function disable-snippet() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    # Generate view name from filename
    FILE=$(basename "$2")
    NAME="mint_system.${FILE%.xml}"

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

function remove-snippet() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    # Generate view name from filename
    FILE=$(basename "$2")
    NAME="mint_system.${FILE%.xml}"

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

function lint-snippets() {

    echo "Lint snippets files..."

    FILES="snippets/*.xml"

    for FILE in $FILES; do

        # Access various snippet parameters
        FILENAME=$(basename "$FILE")
        SNIPPET_MODULE=$(echo "$FILENAME" | cut -d. -f1)
        REPORT=$(echo "$FILENAME" | cut -d. -f2)
        EDIT=$(echo "$FILENAME" | cut -d. -f3)
        INHERIT_ID=$(echo 'cat //data/@inherit_id' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}')
        TYPE=$(echo 'cat //data/@type' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}')
        MODEL=$(echo 'cat //data/@model' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}')
        PRIORITY=$(echo 'cat //data/@priority' | xmllint --shell "$FILE" | awk -F'[="]' '!/>/{print $(NF-1)}')
        MODULE_NAME=$(echo "$INHERIT_ID" | cut -d. -f 1 )
        XML_ID=$(echo "$INHERIT_ID" | cut -d. -f 2 )

        # Check filename
        COUNT_DOTS=$(echo "$FILENAME" | grep -o "\." | wc -l)
        if [ "$COUNT_DOTS" -ge 4 ]; then
            echo "Linting failed for $FILE"
            echo "Dots count: $COUNT_DOTS"
            echo "  Filename: $SNIPPET_MODULE.$REPORT"
            exit 1
        fi

        # Check if inherit id matches the filename
        if [ -n "$INHERIT_ID" ]; then
            if [[ "$INHERIT_ID" != "$SNIPPET_MODULE.$REPORT" ]]; then
                echo "Linting failed for $FILE"
                echo " Inherit: $INHERIT_ID"
                echo "Filename: $SNIPPET_MODULE.$REPORT"
                exit 1
            fi
        fi

        # Check if priority is set
        if [ -z "$PRIORITY" ] && [ -n "$INHERIT_ID" ]; then
            echo "Linting failed for $FILE"
            echo "No priority is set."
            exit 1
        fi
    done
    echo "No problems with snippets found."
}

# Git Commands

function add-git-submodule() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi

    # Add to .gitmodules
    git submodule add -f -b "$ODOO_VERSION" "$1" "$2"

    # Ensure branch is Odoo version
    git -C "$2" switch "$ODOO_VERSION"

    # Save version
    save-version

    # Restore staged submodule files
    git restore --staged .gitmodules "$2"

    # Remove from working tree
    # rm -rf .git/modules/"$2" || true
    # git rm --cached "$2" || true
}

function ls-git-folder() {
    if [ -n "$1" ]; then
        echo $(git config --file .gitmodules --get-regexp url | sed -e 's/submodule.//g' -e 's/.url.*//g' | grep "$1" | sort)
    else
        echo $(git config --file .gitmodules --get-regexp url | sed -e 's/submodule.//g' -e 's/.url.*//g' | sort)
    fi
}

function list-git-folder() {
    # Define column widths
    path_width=35
    url_width=50
    branch_width=8

    # Print table header
    printf "| %-${path_width}s | %-${url_width}s | %-${branch_width}s |\n" "PATH" "URL" "BRANCH"
    echo "|$(printf '%*s' $((path_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((url_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((branch_width + 2)) '' | tr ' ' '-')|"

    GIT_FOLDERS=$(ls-git-folder "$1")
    for GIT_FOLDER in $GIT_FOLDERS; do
        URL=$(git config --file .gitmodules --get "submodule.$GIT_FOLDER.url")
        URL=${URL#git@github.com:}
        URL=${URL%.git}
        BRANCH=$(git -C "$GIT_FOLDER" branch --show-current 2>/dev/null || echo "detached")
        printf "| %-${path_width}s | %-${url_width}s | %-${branch_width}s |\n" "$GIT_FOLDER" "$URL" "${BRANCH:-detached}"
    done
}

function remove-git-folder() {
    GIT_FOLDERS=$(ls-git-folder)
    for GIT_FOLDER in $GIT_FOLDERS; do
        echo "Delete git folder $GIT_FOLDER."
        rm -rf "$GIT_FOLDER"
    done
}

function remove-git-submodule() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    echo "Deinit submodule"
    git submodule deinit -f "$1"

    echo "Move submodule to tmp folder"
    mkdir -p tmp/"$1"
    mv "$1" tmp/"$1"

    echo "Remove submodule from work tree"
    git rm -f "$1"

    echo "Remove submodule from .git folder"
    rm -rf .git/modules/"$1"
}

function clone-git-folder() {
    GIT_FOLDERS=$(ls-git-folder "$1")
    for GIT_FOLDER in $GIT_FOLDERS; do

        GIT_FOLDER_PATH=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".path)
        GIT_FOLDER_URL=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".url)
        SHALLOW=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".shallow)
        GIT_FOLDER_BRANCH=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".branch)

        if [ "$SHALLOW" = "true" ]; then
            echo "Clone git folder $GIT_FOLDER (shallow) with $GIT_FOLDER_BRANCH branch"
            git -C "$GIT_FOLDER" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
            git clone --depth 1 --no-single-branch "$GIT_FOLDER_URL" "$GIT_FOLDER_PATH"
        else
            echo "Clone git folder $GIT_FOLDER with $GIT_FOLDER_BRANCH branch"
            git clone --branch="$GIT_FOLDER_BRANCH" "$GIT_FOLDER_URL" "$GIT_FOLDER_PATH"
        fi
    done
}

function download-git-folder() {
    GIT_FOLDERS=$(ls-git-folder "$1")
    for GIT_FOLDER in $GIT_FOLDERS; do

        GIT_FOLDER_PATH=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".path)
        GIT_FOLDER_URL=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".url)
        GIT_FOLDER_BRANCH=$(git config --file .gitmodules --get submodule."$GIT_FOLDER".branch)

        # Convert git url to http url.
        GIT_FOLDER_URL="$(echo $GIT_FOLDER_URL | sed 's|git@|https://|g' | sed 's|m:|m/|g' | sed 's|\.git||g')"

        # Get commit ref from .gitmodule.csv
        COMMIT_REF=$(grep "$GIT_FOLDER_PATH" "versions/$ODOO_VERSION/.gitmodules.csv" | cut -d',' -f2)

        echo "Download $GIT_FOLDER_URL to $GIT_FOLDER_PATH."
        mkdir -p "$GIT_FOLDER_PATH"
        curl -L -o "$GIT_FOLDER_PATH/archive.tar.gz" \
            "$GIT_FOLDER_URL/archive/$COMMIT_REF.tar.gz"

        echo "Extract and delete $GIT_FOLDER_PATH/archive.tar.gz"
        tar -xzf "$GIT_FOLDER_PATH/archive.tar.gz" --strip-components=1 -C "$GIT_FOLDER_PATH"
        rm "$GIT_FOLDER_PATH/archive.tar.gz"
    done
}

function pull-git-folder() {
    GIT_FOLDERS=$(ls-git-folder)
    for GIT_FOLDER in $GIT_FOLDERS; do
        echo "Pull git folder $GIT_FOLDER."
        git -C "$GIT_FOLDER" stash
        UPDATE=$(git config submodule."$GIT_FOLDER".update)
        if [ -z "$UPDATE" ]; then
            git -C "$GIT_FOLDER" pull origin "$ODOO_VERSION"
        fi;
    done
}

function reset-git-folder() {
    GIT_FOLDERS=$(ls-git-folder)
    for GIT_FOLDER in $GIT_FOLDERS; do
        echo "Abort rebase and reset git folder $GIT_FOLDER."
        git -C "$GIT_FOLDER" rebase --abort
        git -C "$GIT_FOLDER" reset --hard
    done
}

function clean-git-folder() {
    GIT_FOLDERS=$(ls-git-folder)
    for GIT_FOLDER in $GIT_FOLDERS; do
        echo "Clean git folder $GIT_FOLDER."
        git -C "$GIT_FOLDER" clean -df
    done
}

function checkout-git-folder() {
    echo "Checkout submodule commits."
    GIT_FOLDERS=$(ls-git-folder)
    for GIT_FOLDER in $GIT_FOLDERS; do

        SHALLOW=$(git config --file .gitmodules --get-regexp submodule."$GIT_FOLDER".shallow | awk '{ print $2 }')
        COMMIT_REF=$(grep "$GIT_FOLDER" "versions/$ODOO_VERSION/.gitmodules.csv" | cut -d',' -f2)

        if [ "$SHALLOW" = "true" ]; then
            echo "Checkout git folder $GIT_FOLDER (shallow) to $COMMIT_REF"
            git -C "$GIT_FOLDER" stash
            git -C "$GIT_FOLDER" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
            git -C "$GIT_FOLDER" fetch origin "$COMMIT_REF"
            git -C "$GIT_FOLDER" checkout "$COMMIT_REF"
        else
            echo "Checkout git folder $GIT_FOLDER to $COMMIT_REF"
            git -C "$GIT_FOLDER" stash
            git -C "$GIT_FOLDER" checkout "$COMMIT_REF"
        fi
    done
}

function switch-git-folder() {
    if [ -n "$1" ]; then
        ODOO_VERSION="$1"
    fi

    GIT_FOLDERS=$(ls-git-folder)
    for GIT_FOLDER in $GIT_FOLDERS; do

        SHALLOW=$(git config --file .gitmodules --get-regexp submodule."$GIT_FOLDER".shallow | awk '{ print $2 }')

        if [ "$SHALLOW" = "true" ]; then
            echo "Fetch branch $ODOO_VERSION (shallow) from origin and then switch."
            git fetch origin "$ODOO_VERSION:$ODOO_VERSION"
            git -C "$GIT_FOLDER" switch "$ODOO_VERSION"
        else
            echo "Switch git folder branch to $ODOO_VERSION: $GIT_FOLDER"
            git -C "$GIT_FOLDER" stash
            git -C "$GIT_FOLDER" switch "$ODOO_VERSION"
        fi
    done
}

function status-git-folder() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    GIT_FOLDERS=$(ls-git-folder "$1")

    echo "Show status in git folders."
    for GIT_FOLDER in $GIT_FOLDERS; do
        git -C "$GIT_FOLDER" status
    done
}

function stage-git-folder() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    GIT_FOLDERS=$(ls-git-folder "$1")

    for GIT_FOLDER in $GIT_FOLDERS; do
        echo "Stage all files in $GIT_FOLDER."
        git -C "$GIT_FOLDER" add --all
    done
}

function commit-git-folder() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    if test -z "$2"; then echo "\$2 is empty."; exit; fi
    GIT_FOLDERS=$(ls-git-folder "$1")

    for GIT_FOLDER in $GIT_FOLDERS; do
        echo "Commit in $GIT_FOLDER."
        git -C "$GIT_FOLDER" commit --message "$2"
    done
}

function push-git-folder() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    GIT_FOLDERS=$(ls-git-folder "$1")

    for GIT_FOLDER in $GIT_FOLDERS; do
        echo "Push in $GIT_FOLDER."
        git -C "$GIT_FOLDER" push
    done
}

function sync-git-folder() {
    echo "Switch and pull submodules."
    switch-git-folder
    pull-git-folder
}

function create-git-feature-branch(){
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    MODULE_NAME=$(basename "$1")
    MODULE_PATH=$(dirname "$1")

    echo "Create feature branch for $MODULE_NAME in ${MODULE_PATH}..."
    cd ./"$MODULE_PATH" || exit
    pwd
    git switch -c "feature-$MODULE_NAME"
}

function create-git-mig-branch(){
    if test -z "$1"; then echo "\$1 is empty."; exit; fi

    MODULE_NAME=$(basename "$1")
    MODULE_PATH=$(dirname "$1")

    echo "Create mig branch for $MODULE_NAME in ${MODULE_PATH}..."
    cd ./"$MODULE_PATH" || exit
    pwd
    git switch -c "$ODOO_VERSION-mig-$MODULE_NAME"
}

function 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" .
}

function checkout() {
    if test -z "$1"; then echo "\$1 is empty."; exit; fi
    ODOO_VERSION="$1"

    # Load .gitmodules and .python-version
    load-version "$ODOO_VERSION"
    # Set .env
    load-revision "$ODOO_VERSION"
    # Clone the addons
    clone-git-folder
    # Checkout commit ref
    checkout-git-folder
    # Upate branch
    switch-git-folder
}

function save-version() {
    GIT_FOLDERS_FILE="versions/$ODOO_VERSION/.gitmodules.csv"
    echo "Save submodule refs to $GIT_FOLDERS_FILE."

    GIT_FOLDERS=$(ls-git-folder)
    echo "path,rev" > "$GIT_FOLDERS_FILE"
    for GIT_FOLDER in $GIT_FOLDERS; do
        COMMIT_REF=$(git -C "$GIT_FOLDER" rev-parse HEAD)
        echo "$GIT_FOLDER,$COMMIT_REF" >> "$GIT_FOLDERS_FILE"
    done

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

# Revision Commands

function list-revision() {
    ls -1 revisions
}

function 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"
}

function 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)
}

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

    load-revision "$ODOO_REVISION"

    echo "Checkout and tag git repos..."
    git -C odoo checkout $ODOO_REF
    git -C odoo tag -a $ODOO_REVISION -m "$ODOO_REVISION"
    git -C enterprise checkout $ODOO_ENTERPRISE_REF
    git -C enterprise tag -a $ODOO_REVISION -m "$ODOO_REVISION"
    git -C themes checkout $ODOO_THEME_REF
    git -C themes tag -a $ODOO_REVISION -m "$ODOO_REVISION"
}

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

    REVISION="$1"
    ODOO_DATE=$(echo "$REVISION" | cut -d'.' -f3-)
    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
    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
    REF=$(git rev-list -n 1 --before="$BEFORE_DATE" origin/$ODOO_VERSION)
    git checkout "$REF"
    cd ..

    cd enterprise
    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
    ENTERPRISE_REF=$(git rev-list -n 1 --before="$BEFORE_DATE" origin/$ODOO_VERSION)
    git checkout "$ENTERPRISE_REF"
    cd ..

    cd themes
    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
    THEME_REF=$(git rev-list -n 1 --before="$BEFORE_DATE" origin/$ODOO_VERSION)
    git checkout "$THEME_REF"
    cd ..

    echo -e "\nCreate revision: $REVISION"
    export REVISION REF ENTERPRISE_REF THEME_REF
    envsubst '$REVISION $REF $ENTERPRISE_REF $THEME_REF' < templates/odoo_revision > "revisions/$REVISION"
    chmod +x "revisions/$REVISION"

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

function commit-revision() {
    echo "Commit and and push $ODOO_REVISION tag..."
    git commit --all --message "feat(revision): release $ODOO_REVISION"
    git tag "$ODOO_REVISION"
    git push --tags
}

# RPC Commands

function test-xmlrpc() {
    activate-venv

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

function odoocli(){
    activate-venv
    images/odoocli/bin/odoocli "$@"
}

function export-website-data() {
    activate-venv
    if [ -n "$1" ]; then
        load-env $1
    fi

    export ODOO_URL
    export ODOO_DATABASE
    export ODOO_USERNAME
    export ODOO_PASSWORD
    export ODOO_WEBSITE_ID

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

function import-website-data() {
    activate-venv
    if [ -n "$1" ]; then
        load-env $1
    fi

    export ODOO_URL
    export ODOO_DATABASE
    export ODOO_USERNAME
    export ODOO_PASSWORD
    export ODOO_WEBSITE_ID

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

# Vuepress Commands

function install-vuepress() {
    echo "Install Node."
    pnpm env use --global lts

    echo "Install npm packages."
    pnpm install
}

function dev-vuepress() {
    pnpm run dev
}

function build-vuepress() {
    pnpm run build
}

function serve-vuepress() {
    cd .vuepress/dist
    pnpx serve
}

if declare -f "$1" > /dev/null; then
    "$1" "${@:2}"
else
    case "$1" in
        source)
            activate-venv
            ;;
        *)
            echo "Unknown command: $1"
            help
            exit 1
            ;;
    esac
fi
