SHELL := /usr/bin/env bash
DOTFILES_PATH := $(CURDIR)
DOCKER_CONTEXT ?=
DOCKER_PLATFORM ?=
DOCKER := docker
INSTALL_MODE ?= interactive

ifneq ($(DOCKER_CONTEXT),)
DOCKER := docker --context $(DOCKER_CONTEXT)
endif

DOCKER_PLATFORM_FLAG = $(if $(DOCKER_PLATFORM),--platform $(DOCKER_PLATFORM),)

.DEFAULT_GOAL := help

.PHONY: help check check-bootstrap check-fish check-deps-script check-deps-manifest test-install docker-check docker-deps-check docker-deps-update-lock deps-list deps-check deps-plan deps-update-lock doctor deps install bootstrap lint-shell fmt-shell check-shellfmt

help:
	@echo "Usage: make <target>"
	@echo ""
	@echo "Targets:"
	@echo "  help          Show this help message"
	@echo "  deps          Install required software for this dotfiles setup"
	@echo "  bootstrap     Install dependencies, dotfiles, then run doctor"
	@echo "  check         Validate scripts and test install in a temporary HOME"
	@echo "  docker-check  Run checks inside a clean Ubuntu container"
	@echo "  docker-deps-check"
	@echo "                Verify dependency installation inside Ubuntu"
	@echo "                Use DOCKER_PLATFORM=linux/amd64 for alternate arch"
	@echo "  doctor        Validate the current machine and real HOME symlinks"
	@echo "  install       Install dotfiles into the real HOME"
	@echo "                Use INSTALL_MODE=backup|overwrite|skip for non-interactive mode"
	@echo "  check-bootstrap"
	@echo "                Check bootstrap bash syntax"
	@echo "  check-fish    Check fish config syntax when fish is installed"
	@echo "  check-deps-script"
	@echo "                Check dependency installer bash syntax"
	@echo "  check-deps-manifest"
	@echo "                Check dependency manifest can be parsed"
	@echo "  lint-shell    Run shellcheck across repository-owned shell scripts"
	@echo "  fmt-shell     Format repository-owned shell scripts with shfmt"
	@echo "  check-shellfmt"
	@echo "                Check shell formatting without rewriting files"
	@echo "  deps-list     List configured dependency versions"
	@echo "  deps-check    Check installed dependency versions"
	@echo "  deps-plan     Show planned dependency actions"
	@echo "  deps-update-lock"
	@echo "                Refresh deps.lock for the current platform"
	@echo "  docker-deps-update-lock"
	@echo "                Refresh deps.lock for Linux inside Ubuntu"
	@echo "  docker-deps-update-lock-amd64"
	@echo "                Refresh deps.lock for Linux/amd64 inside Ubuntu"
	@echo "  test-install  Verify bootstrap creates expected links in a temporary HOME"

check: check-bootstrap check-fish check-deps-script check-deps-manifest test-install

check-bootstrap:
	bash -n bootstrap

check-deps-script:
	bash -n scripts/install-deps

check-deps-manifest:
	scripts/install-deps list >/dev/null

lint-shell:
	@set -euo pipefail; \
	if ! command -v shellcheck >/dev/null 2>&1; then \
		echo "error: shellcheck is required for lint-shell" >&2; \
		exit 1; \
	fi; \
	files="$$(find . \
		-path './.git' -prune -o \
		-path './__pycache__' -prune -o \
		-type f -perm -111 \
		-print | sort)"; \
	if [ -z "$$files" ]; then \
		exit 0; \
	fi; \
	for file in $$files; do \
		case "$$(head -n 1 "$$file")" in \
			'#!/bin/sh'*|'#!/usr/bin/env sh'*|'#!/bin/bash'*|'#!/usr/bin/env bash'*|'#!/bin/zsh'*|'#!/usr/bin/env zsh'*|'#!/usr/bin/env -S sh '*|'#!/usr/bin/env -S bash '*|'#!/usr/bin/env -S zsh '*) shellcheck "$$file" ;; \
		esac; \
	done

fmt-shell:
	@set -euo pipefail; \
	if ! command -v shfmt >/dev/null 2>&1; then \
		echo "error: shfmt is required for fmt-shell" >&2; \
		exit 1; \
	fi; \
	files="$$(find . \
		-path './.git' -prune -o \
		-path './__pycache__' -prune -o \
		-type f -perm -111 \
		-print | sort)"; \
	if [ -z "$$files" ]; then \
		exit 0; \
	fi; \
	for file in $$files; do \
		case "$$(head -n 1 "$$file")" in \
			'#!/bin/sh'*|'#!/usr/bin/env sh'*|'#!/bin/bash'*|'#!/usr/bin/env bash'*|'#!/bin/zsh'*|'#!/usr/bin/env zsh'*|'#!/usr/bin/env -S sh '*|'#!/usr/bin/env -S bash '*|'#!/usr/bin/env -S zsh '*) shfmt -w "$$file" ;; \
		esac; \
	done

check-shellfmt:
	@set -euo pipefail; \
	if ! command -v shfmt >/dev/null 2>&1; then \
		echo "error: shfmt is required for check-shellfmt" >&2; \
		exit 1; \
	fi; \
	files="$$(find . \
		-path './.git' -prune -o \
		-path './__pycache__' -prune -o \
		-type f -perm -111 \
		-print | sort)"; \
	if [ -z "$$files" ]; then \
		exit 0; \
	fi; \
	for file in $$files; do \
		case "$$(head -n 1 "$$file")" in \
			'#!/bin/sh'*|'#!/usr/bin/env sh'*|'#!/bin/bash'*|'#!/usr/bin/env bash'*|'#!/bin/zsh'*|'#!/usr/bin/env zsh'*|'#!/usr/bin/env -S sh '*|'#!/usr/bin/env -S bash '*|'#!/usr/bin/env -S zsh '*) shfmt -d "$$file" ;; \
		esac; \
	done

check-fish:
	@if command -v fish >/dev/null 2>&1; then \
		fish -n .config/fish/config.fish; \
	else \
		echo "skip fish syntax check: fish not installed"; \
	fi

test-install:
	@tmp_home="$$(mktemp -d)"; \
	trap 'rm -rf "$$tmp_home"' EXIT; \
	HOME="$$tmp_home" bash bootstrap >/dev/null; \
	test -L "$$tmp_home/.tmux.conf"; \
	test -L "$$tmp_home/.config/fish"; \
	test -L "$$tmp_home/.config/nvim"; \
	test -L "$$tmp_home/.config/ghostty"; \
	echo "temporary install check passed"

docker-check:
	$(DOCKER) run --rm $(DOCKER_PLATFORM_FLAG) \
		-v "$(DOTFILES_PATH):/dotfiles:ro" \
		-w /dotfiles \
		ubuntu:24.04 \
		bash -lc 'export DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC && apt-get update && apt-get install -y --no-install-recommends bash make fish tmux neovim git && make check'

docker-deps-check:
	$(DOCKER) run --rm $(DOCKER_PLATFORM_FLAG) \
		-v "$(DOTFILES_PATH):/dotfiles:ro" \
		-w /dotfiles \
		ubuntu:24.04 \
		bash -lc 'export PATH="$$HOME/.local/bin:$$PATH" && scripts/install-deps install --only git,make,fish,ghostty,nvim,starship,yazi,zoxide,eza,lazygit && scripts/install-deps check --only git,make,fish,ghostty,nvim,starship,yazi,zoxide,eza,lazygit'

docker-deps-update-lock:
	$(DOCKER) run --rm $(DOCKER_PLATFORM_FLAG) \
		-v "$(DOTFILES_PATH):/dotfiles" \
		-w /dotfiles \
		ubuntu:24.04 \
		bash -lc 'export DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC && apt-get update && apt-get install -y --no-install-recommends python3 && export PATH="$$HOME/.local/bin:$$PATH" && LOCK_STRICT=0 scripts/install-deps install --only git,make,tmux,fish,ghostty,nvim,starship,yazi,zoxide,eza,lazygit && scripts/install-deps update-lock'

docker-deps-update-lock-amd64: DOCKER_PLATFORM := linux/amd64
docker-deps-update-lock-amd64: docker-deps-update-lock

doctor:
	@status=0; \
	check_cmd() { \
		if command -v "$$1" >/dev/null 2>&1; then \
			echo "ok: $$1"; \
		else \
			echo "missing: $$1"; \
			status=1; \
		fi; \
	}; \
	check_link() { \
		target="$$1"; \
		expected="$$2"; \
		if [ ! -L "$$target" ]; then \
			echo "missing link: $$target"; \
			status=1; \
			return; \
		fi; \
		actual="$$(readlink "$$target")"; \
		if [ "$$actual" = "$$expected" ]; then \
			echo "ok: $$target -> $$actual"; \
		else \
			echo "mismatch: $$target -> $$actual, expected $$expected"; \
			status=1; \
		fi; \
	}; \
	check_ghostty() { \
		if [ "$$(uname -s)" = Darwin ]; then \
			if command -v ghostty >/dev/null 2>&1; then \
				echo "ok: ghostty"; \
			elif [ -d "/Applications/Ghostty.app" ]; then \
				echo "ok: /Applications/Ghostty.app"; \
			else \
				echo "missing: ghostty"; \
				status=1; \
			fi; \
			return; \
		fi; \
		check_cmd ghostty; \
	}; \
	for cmd in git tmux nvim fish ghostty yazi zoxide starship eza lazygit; do \
		if [ "$$cmd" = ghostty ]; then \
			check_ghostty; \
		else \
			check_cmd "$$cmd"; \
		fi; \
	done; \
	check_link "$$HOME/.tmux.conf" "$(DOTFILES_PATH)/.tmux.conf"; \
	check_link "$$HOME/.config/fish" "$(DOTFILES_PATH)/.config/fish"; \
	check_link "$$HOME/.config/nvim" "$(DOTFILES_PATH)/.config/nvim"; \
	check_link "$$HOME/.config/ghostty" "$(DOTFILES_PATH)/.config/ghostty"; \
	if command -v fish >/dev/null 2>&1; then \
		fish -n "$(DOTFILES_PATH)/.config/fish/config.fish" || status=1; \
	fi; \
	if command -v nvim >/dev/null 2>&1; then \
		nvim_tmp="$$(mktemp -d)"; \
		NVIM_LOG_FILE="$$nvim_tmp/nvim.log" nvim --headless --clean +'quitall' >/dev/null || status=1; \
		rm -rf "$$nvim_tmp"; \
	fi; \
	if command -v tmux >/dev/null 2>&1; then \
		tmux_tmp="$$(mktemp -d)"; \
		tmux_output="$$(TMUX_TMPDIR="$$tmux_tmp" tmux -L dotfiles-doctor -f "$(DOTFILES_PATH)/.tmux.conf" new-session -d 2>&1)"; \
		tmux_status="$$?"; \
		if [ "$$tmux_status" -eq 0 ]; then \
			TMUX_TMPDIR="$$tmux_tmp" tmux -L dotfiles-doctor kill-server >/dev/null 2>&1 || true; \
			echo "ok: tmux config"; \
		elif [[ "$$tmux_output" == *"Operation not permitted"* ]]; then \
			echo "warn: tmux config check skipped: $$tmux_output"; \
		else \
			echo "$$tmux_output"; \
			status=1; \
		fi; \
		rm -rf "$$tmux_tmp"; \
	fi; \
	exit $$status

install:
	INSTALL_MODE="$(INSTALL_MODE)" bash bootstrap

deps:
	bash scripts/install-deps

deps-list:
	scripts/install-deps list

deps-check:
	scripts/install-deps check

deps-plan:
	scripts/install-deps plan

deps-update-lock:
	scripts/install-deps update-lock

bootstrap:
	$(MAKE) deps
	$(MAKE) install INSTALL_MODE=backup
	$(MAKE) doctor
